namillum
Version:
Bubble Protocol SDK
1,807 lines • 59.6 kB
text/typescript
import { scillaJSONParams, scillaJSONVal } from "@zilliqa-js/scilla-json-utils";
import { BN, Zilliqa } from "@zilliqa-js/zilliqa";
import { expect } from "@jest/globals";
import fs from "fs";
import { getAddressFromPrivateKey, schnorr } from "@zilliqa-js/crypto";
import {
getBNum,
increaseBNum,
getErrorMsg,
ZERO_ADDRESS,
expectTransitions,
BalanceTracker,
expectEvents,
zilBalancesGetter,
zrc2BalancesGetter,
zrc2AllowncesGetter,
expectDeltas,
expectTokenOwners,
} from "./testutils";
import {
API,
TX_PARAMS,
CONTRACTS,
FAUCET_PARAMS,
FIXED_PRICE_ERROR,
asyncNoop,
} from "./config";
const JEST_WORKER_ID = Number(process.env["JEST_WORKER_ID"]);
const GENESIS_PRIVATE_KEY = global.GENESIS_PRIVATE_KEYS[JEST_WORKER_ID - 1];
const zilliqa = new Zilliqa(API);
zilliqa.wallet.addByPrivateKey(GENESIS_PRIVATE_KEY);
let globalBNum;
let globalTokenAddress;
let globalPaymentTokenAddress;
let globalMarketplaceAddress;
let globalNotAllowedPaymentTokenAddress;
let globalTestAccounts: Array<{
privateKey: string;
address: string;
}> = [];
const SELLER = 0;
const BUYER = 1;
const MARKETPLACE_CONTRACT_OWNER = 2;
const STRANGER = 3;
const FORBIDDEN = 4;
const getTestAddr = (index) => globalTestAccounts[index]?.address as string;
beforeAll(async () => {
const accounts = Array.from({ length: 5 }, schnorr.generatePrivateKey).map(
(privateKey) => ({
privateKey,
address: getAddressFromPrivateKey(privateKey),
})
);
for (const { privateKey, address } of accounts) {
zilliqa.wallet.addByPrivateKey(privateKey);
const tx = await zilliqa.blockchain.createTransaction(
zilliqa.transactions.new(
{
...FAUCET_PARAMS,
toAddr: address,
},
false
)
);
if (!tx.getReceipt()?.success) {
throw new Error();
}
}
globalTestAccounts = accounts;
console.table({
GENESIS_PRIVATE_KEY,
SELLER: getTestAddr(SELLER),
BUYER: getTestAddr(BUYER),
MARKETPLACE_CONTRACT_OWNER: getTestAddr(MARKETPLACE_CONTRACT_OWNER),
STRANGER: getTestAddr(STRANGER),
FORBIDDEN: getTestAddr(FORBIDDEN),
});
zilliqa.wallet.setDefault(getTestAddr(STRANGER));
const [, contract] = await zilliqa.contracts
.new(
fs.readFileSync(CONTRACTS.wzil.path).toString(),
scillaJSONParams({
_scilla_version: ["Uint32", 0],
contract_owner: ["ByStr20", getTestAddr(STRANGER)],
name: ["String", CONTRACTS.wzil.name],
symbol: ["String", CONTRACTS.wzil.symbol],
decimals: ["Uint32", CONTRACTS.wzil.decimal],
init_supply: ["Uint128", CONTRACTS.wzil.initial_supply],
})
)
.deploy(TX_PARAMS, 33, 1000, true);
globalNotAllowedPaymentTokenAddress = contract.address;
});
beforeEach(async () => {
globalBNum = await getBNum(zilliqa);
// SELLER is the zrc6 contract owner
zilliqa.wallet.setDefault(getTestAddr(SELLER));
let [, contract] = await zilliqa.contracts
.new(
fs.readFileSync(CONTRACTS.zrc6.path).toString(),
scillaJSONParams({
_scilla_version: ["Uint32", 0],
initial_contract_owner: ["ByStr20", getTestAddr(SELLER)],
initial_base_uri: ["String", CONTRACTS.zrc6.baseURI],
name: ["String", CONTRACTS.zrc6.name],
symbol: ["String", CONTRACTS.zrc6.symbol],
})
)
.deploy(TX_PARAMS, 33, 1000, true);
globalTokenAddress = contract.address;
if (globalTokenAddress === undefined) {
throw new Error();
}
// BUYER is the WZIL contract owner
zilliqa.wallet.setDefault(getTestAddr(BUYER));
[, contract] = await zilliqa.contracts
.new(
fs.readFileSync(CONTRACTS.wzil.path).toString(),
scillaJSONParams({
_scilla_version: ["Uint32", 0],
contract_owner: ["ByStr20", getTestAddr(BUYER)],
name: ["String", CONTRACTS.wzil.name],
symbol: ["String", CONTRACTS.wzil.symbol],
decimals: ["Uint32", CONTRACTS.wzil.decimal],
init_supply: ["Uint128", CONTRACTS.wzil.initial_supply],
})
)
.deploy(TX_PARAMS, 33, 1000, true);
globalPaymentTokenAddress = contract.address;
if (globalPaymentTokenAddress === undefined) {
throw new Error();
}
// MARKETPLACE_CONTRACT_OWNER is the allowlist contract owner
zilliqa.wallet.setDefault(getTestAddr(MARKETPLACE_CONTRACT_OWNER));
[, contract] = await zilliqa.contracts
.new(
fs.readFileSync(CONTRACTS.allowlist.path).toString(),
scillaJSONParams({
_scilla_version: ["Uint32", 0],
initial_contract_owner: [
"ByStr20",
getTestAddr(MARKETPLACE_CONTRACT_OWNER),
],
})
)
.deploy(TX_PARAMS, 33, 1000, true);
const localAllowlistAddress = contract.address;
if (localAllowlistAddress === undefined) {
throw new Error();
}
// MARKETPLACE_CONTRACT_OWNER is the zrc6 marketplace contract owner
zilliqa.wallet.setDefault(getTestAddr(MARKETPLACE_CONTRACT_OWNER));
[, contract] = await zilliqa.contracts
.new(
fs.readFileSync(CONTRACTS.fixed_price.path).toString(),
scillaJSONParams({
_scilla_version: ["Uint32", 0],
initial_contract_owner: [
"ByStr20",
getTestAddr(MARKETPLACE_CONTRACT_OWNER),
],
})
)
.deploy(TX_PARAMS, 33, 1000, true);
globalMarketplaceAddress = contract.address;
if (globalMarketplaceAddress === undefined) {
throw new Error();
}
for (const call of [
{
beforeTransition: asyncNoop,
sender: getTestAddr(MARKETPLACE_CONTRACT_OWNER),
contract: localAllowlistAddress,
transition: "Allow",
transitionParams: scillaJSONParams({
address_list: [
"List (ByStr20)",
[
getTestAddr(MARKETPLACE_CONTRACT_OWNER),
getTestAddr(SELLER),
getTestAddr(BUYER),
getTestAddr(STRANGER),
],
],
}),
txParams: TX_PARAMS,
},
{
beforeTransition: asyncNoop,
sender: getTestAddr(MARKETPLACE_CONTRACT_OWNER),
contract: globalMarketplaceAddress,
transition: "SetAllowlist",
transitionParams: scillaJSONParams({
address: ["ByStr20", localAllowlistAddress],
}),
txParams: TX_PARAMS,
},
{
beforeTransition: asyncNoop,
sender: getTestAddr(SELLER),
contract: globalTokenAddress,
transition: "BatchMint",
transitionParams: scillaJSONParams({
to_token_uri_pair_list: [
"List (Pair (ByStr20) (String))",
[
[getTestAddr(SELLER), ""],
[getTestAddr(SELLER), ""],
[getTestAddr(SELLER), ""],
],
],
}),
txParams: TX_PARAMS,
},
{
beforeTransition: asyncNoop,
sender: getTestAddr(MARKETPLACE_CONTRACT_OWNER),
contract: globalMarketplaceAddress,
transition: "AllowPaymentTokenAddress",
transitionParams: scillaJSONParams({
address: ["ByStr20", globalPaymentTokenAddress],
}),
txParams: TX_PARAMS,
},
{
beforeTransition: asyncNoop,
sender: getTestAddr(BUYER),
contract: globalPaymentTokenAddress,
transition: "IncreaseAllowance",
transitionParams: scillaJSONParams({
spender: ["ByStr20", globalMarketplaceAddress],
amount: ["Uint128", 100 * 1000],
}),
txParams: TX_PARAMS,
},
...[1, 2, 3].map((tokenId) => ({
beforeTransition: asyncNoop,
sender: getTestAddr(SELLER),
contract: globalTokenAddress,
transition: "SetSpender",
transitionParams: scillaJSONParams({
spender: ["ByStr20", globalMarketplaceAddress],
token_id: ["Uint256", tokenId],
}),
txParams: TX_PARAMS,
})),
]) {
await call.beforeTransition();
zilliqa.wallet.setDefault(call.sender);
const tx: any = await zilliqa.contracts
.at(call.contract)
.call(call.transition, call.transitionParams, call.txParams);
if (!tx.receipt.success) {
throw new Error();
}
}
});
describe("Native ZIL", () => {
beforeEach(async () => {
for (const call of [
{
beforeTransition: asyncNoop,
sender: getTestAddr(SELLER),
contract: globalMarketplaceAddress,
transition: "SetOrder",
transitionParams: scillaJSONParams({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
txParams: TX_PARAMS,
},
{
beforeTransition: asyncNoop,
sender: getTestAddr(BUYER),
contract: globalMarketplaceAddress,
transition: "SetOrder",
transitionParams: scillaJSONParams({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 5],
}),
txParams: { ...TX_PARAMS, amount: new BN("10000") },
},
]) {
await call.beforeTransition();
zilliqa.wallet.setDefault(call.sender);
const tx: any = await zilliqa.contracts
.at(call.contract)
.call(call.transition, call.transitionParams, call.txParams);
if (!tx.receipt.success) {
throw new Error();
}
}
const res = await zilliqa.blockchain.getBalance(globalMarketplaceAddress);
expect(res.result.balance).toBe("10000");
});
const testCases = [
{
name: "throws NotAllowedUserError",
transition: "SetOrder",
getSender: () => getTestAddr(FORBIDDEN),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotAllowedUserError,
want: undefined,
},
{
name: "throws NotTokenOwnerError (stranger creates sell order for token #1)",
transition: "SetOrder",
getSender: () => getTestAddr(STRANGER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotTokenOwnerError,
want: undefined,
},
{
name: "throws TokenOwnerError (seller creates buy order for token #1)",
transition: "SetOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.TokenOwnerError,
want: undefined,
},
{
name: "throws NotSelfError (stranger must not update the order)",
transition: "SetOrder",
txAmount: 10000,
getSender: () => getTestAddr(STRANGER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 10],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotSelfError,
want: undefined,
},
{
name: "buyer updates expiration_bnum of buy order",
transition: "SetOrder",
txAmount: 0, // _amount is irelevant for the update operaton
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", 99999],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: 0,
}),
events: [
{
name: "SetOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(BUYER).toLowerCase()],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
expiration_bnum: ["BNum", (99999).toString()],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: {
[ZERO_ADDRESS.toLowerCase()]: {
[10000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(BUYER), 99999]
),
},
},
},
})
);
},
},
},
{
name: "Seller creates sell order for token #1",
transition: "SetOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
events: [
{
name: "SetOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(SELLER).toLowerCase()],
side: ["Uint32", 0],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
expiration_bnum: ["BNum", (globalBNum + 5).toString()],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.sell_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: {
[ZERO_ADDRESS]: {
[10000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(SELLER), globalBNum + 5]
),
[20000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(SELLER), globalBNum + 5]
),
},
},
},
})
);
},
},
},
{
name: "Buyer creates buy order for token #1",
transition: "SetOrder",
txAmount: 20000,
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 20000,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: -20000,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
events: [
{
name: "SetOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(BUYER).toLowerCase()],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 20000],
expiration_bnum: ["BNum", (globalBNum + 5).toString()],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
["1"]: {
[ZERO_ADDRESS]: {
[10000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(BUYER), globalBNum + 5]
),
[20000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(BUYER), globalBNum + 5]
),
},
},
},
})
);
},
},
},
{
name: "throws NotAllowedUserError",
transition: "FulfillOrder",
txAmount: 10000,
getSender: () => getTestAddr(FORBIDDEN),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotAllowedUserError,
want: undefined,
},
{
name: "throws SellOrderNotFoundError",
transition: "FulfillOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 999],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.SellOrderNotFoundError,
want: undefined,
},
{
name: "throws BuyOrderNotFoundError",
transition: "FulfillOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 999],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
dest: ["ByStr20", getTestAddr(SELLER)],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.BuyOrderNotFoundError,
want: undefined,
},
{
name: "throws ExpiredError",
transition: "FulfillOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: async () => {
await increaseBNum(zilliqa, 5);
},
error: FIXED_PRICE_ERROR.ExpiredError,
want: undefined,
},
{
name: "throws NotEqualAmountError",
transition: "FulfillOrder",
txAmount: 1000,
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotEqualAmountError,
want: undefined,
},
{
name: "Buyer fullfills sell order",
transition: "FulfillOrder",
txAmount: 10000,
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(BUYER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 9750,
[getTestAddr(BUYER)]: -10000,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 250,
[getTestAddr(STRANGER)]: 0,
}),
events: [
{
name: "FulfillOrder",
getParams: () => ({
taker: ["ByStr20", getTestAddr(BUYER)],
side: ["Uint32", 0],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
seller: ["ByStr20", getTestAddr(SELLER)],
buyer: ["ByStr20", getTestAddr(BUYER)],
asset_recipient: ["ByStr20", getTestAddr(BUYER)],
payment_tokens_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_amount: ["Uint128", 1000],
service_fee: ["Uint128", 250],
}),
},
// NFT transfer
{
name: "TransferFrom",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
],
transitions: [
{
amount: 1000,
recipient: getTestAddr(SELLER),
tag: "AddFunds",
getParams: () => ({}),
},
{
amount: 250,
recipient: getTestAddr(MARKETPLACE_CONTRACT_OWNER),
tag: "AddFunds",
getParams: () => ({}),
},
{
amount: 8750,
recipient: getTestAddr(SELLER),
tag: "AddFunds",
getParams: () => ({}),
},
// NFT transfer
{
tag: "TransferFrom",
getParams: () => ({
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
{
tag: "ZRC6_RecipientAcceptTransferFrom",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
{
tag: "ZRC6_TransferFromCallback",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.sell_orders)).toBe(
JSON.stringify({ [globalTokenAddress.toLowerCase()]: {} })
);
},
},
},
{
name: "Seller fullfills buy order",
transition: "FulfillOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(BUYER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: -10000,
[getTestAddr(SELLER)]: 1000 + 8750,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 250,
[getTestAddr(STRANGER)]: 0,
}),
events: [
{
name: "FulfillOrder",
getParams: () => ({
taker: ["ByStr20", getTestAddr(SELLER)],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
seller: ["ByStr20", getTestAddr(SELLER)],
buyer: ["ByStr20", getTestAddr(BUYER)],
asset_recipient: ["ByStr20", getTestAddr(BUYER)],
payment_tokens_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_amount: ["Uint128", 1000],
service_fee: ["Uint128", 250],
}),
},
// NFT transfer
{
name: "TransferFrom",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
],
transitions: [
{
amount: 1000,
recipient: getTestAddr(SELLER),
tag: "AddFunds",
getParams: () => ({}),
},
{
amount: 250,
recipient: getTestAddr(MARKETPLACE_CONTRACT_OWNER),
tag: "AddFunds",
getParams: () => ({}),
},
{
amount: 8750,
recipient: getTestAddr(SELLER),
tag: "AddFunds",
getParams: () => ({}),
},
// NFT transfer
{
tag: "TransferFrom",
getParams: () => ({
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
{
tag: "ZRC6_RecipientAcceptTransferFrom",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
{
tag: "ZRC6_TransferFromCallback",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: { [ZERO_ADDRESS]: {} },
},
})
);
},
},
},
{
name: "throws NotAllowedToCancelOrder by stranger",
transition: "CancelOrder",
getSender: () => getTestAddr(STRANGER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotAllowedToCancelOrder,
},
{
name: "Buyer cancels buy order",
transition: "CancelOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: -10000,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 10000,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
events: [
{
name: "CancelOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(BUYER)],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: { [ZERO_ADDRESS]: {} },
},
})
);
},
},
},
{
name: "Seller cancels sell order",
transition: "CancelOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
events: [
{
name: "CancelOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(SELLER)],
side: ["Uint32", 0],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", ZERO_ADDRESS],
sale_price: ["Uint128", 10000],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.sell_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: { [ZERO_ADDRESS]: {} },
},
})
);
},
},
},
];
for (const testCase of testCases) {
it(`${testCase.transition}: ${testCase.name}`, async () => {
await testCase.beforeTransition();
let balanceTracker;
if (testCase.want) {
balanceTracker = new BalanceTracker(
zilliqa,
Object.keys(await testCase.want.getBalanceDeltas()),
zilBalancesGetter(zilliqa)
);
await balanceTracker.init();
}
zilliqa.wallet.setDefault(testCase.getSender());
const tx: any = await zilliqa.contracts
.at(globalMarketplaceAddress)
.call(testCase.transition, scillaJSONParams(testCase.getParams()), {
...TX_PARAMS,
amount: new BN(testCase.txAmount || 0),
});
if (testCase.want === undefined) {
// Negative Cases
expect(tx.receipt.success).toBe(false);
expect(tx.receipt.exceptions[0].message).toBe(
getErrorMsg(testCase.error)
);
} else {
// Positive Cases
if (!tx.receipt.success) {
console.log(JSON.stringify(tx.receipt));
}
expect(tx.receipt.success).toBe(true);
expectEvents(tx.receipt.event_logs, testCase.want.events);
testCase.want.transitions &&
expectTransitions(tx.receipt.transitions, testCase.want.transitions);
testCase.want.expectState(
await zilliqa.contracts.at(globalMarketplaceAddress).getState()
);
expectTokenOwners(
(await zilliqa.contracts.at(globalTokenAddress).getState())[
"token_owners"
],
testCase.want.getTokenOwners()
);
expectDeltas(
await balanceTracker.deltas(),
await testCase.want?.getBalanceDeltas(),
tx,
testCase.getSender()
);
}
});
}
});
describe("WZIL", () => {
beforeEach(async () => {
for (const call of [
{
beforeTransition: asyncNoop,
sender: getTestAddr(SELLER),
contract: globalMarketplaceAddress,
transition: "SetOrder",
transitionParams: scillaJSONParams({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
txParams: TX_PARAMS,
},
{
beforeTransition: asyncNoop,
sender: getTestAddr(BUYER),
contract: globalMarketplaceAddress,
transition: "SetOrder",
transitionParams: scillaJSONParams({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 5],
}),
txParams: { ...TX_PARAMS },
},
]) {
await call.beforeTransition();
zilliqa.wallet.setDefault(call.sender);
const tx: any = await zilliqa.contracts
.at(call.contract)
.call(call.transition, call.transitionParams, call.txParams);
if (!tx.receipt.success) {
throw new Error();
}
}
const res = await zilliqa.blockchain.getBalance(globalMarketplaceAddress);
expect(res.result.balance).toBe("0");
});
const testCases = [
{
name: "throws NotAllowedPaymentToken",
transition: "SetOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalNotAllowedPaymentTokenAddress],
sale_price: ["Uint128", 20000],
side: ["Uint32", 0], // 0 is sell, 1 is buy,
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotAllowedPaymentToken,
want: undefined,
},
{
name: "throws NotTokenOwnerError (stranger creates sell order for token #1)",
transition: "SetOrder",
getSender: () => getTestAddr(STRANGER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 20000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotTokenOwnerError,
want: undefined,
},
{
name: "throws TokenOwnerError (seller must not create a buy order for token #1)",
transition: "SetOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 20000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.TokenOwnerError,
want: undefined,
},
{
name: "throws NotSelfError (stranger must not update the order)",
transition: "SetOrder",
getSender: () => getTestAddr(STRANGER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 10],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotSelfError,
want: undefined,
},
{
name: "buyer updates expiration_bnum of buy order",
transition: "SetOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", 99999],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: 0,
}),
events: [
{
name: "SetOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(BUYER).toLowerCase()],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
expiration_bnum: ["BNum", (99999).toString()],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: {
[globalPaymentTokenAddress.toLowerCase()]: {
[10000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(BUYER), 99999]
),
},
},
},
})
);
},
},
},
{
name: "Seller creates sell order for token #1",
transition: "SetOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 20000],
side: ["Uint32", 0],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: 0,
}),
events: [
{
name: "SetOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(SELLER).toLowerCase()],
side: ["Uint32", 0],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 20000],
expiration_bnum: ["BNum", (globalBNum + 5).toString()],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.sell_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: {
[globalPaymentTokenAddress.toLowerCase()]: {
[10000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(SELLER), globalBNum + 5]
),
[20000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(SELLER), globalBNum + 5]
),
},
},
},
})
);
},
},
},
{
name: "Buyer creates buy order for token #1",
transition: "SetOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 20000],
side: ["Uint32", 1],
expiration_bnum: ["BNum", globalBNum + 5],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: 0,
}),
events: [
{
name: "SetOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(BUYER).toLowerCase()],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 20000],
expiration_bnum: ["BNum", (globalBNum + 5).toString()],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: {
[globalPaymentTokenAddress.toLowerCase()]: {
[10000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(BUYER), globalBNum + 5]
),
[20000]: scillaJSONVal(
`${globalMarketplaceAddress}.Order.Order.of.ByStr20.BNum`,
[getTestAddr(BUYER), globalBNum + 5]
),
},
},
},
})
);
},
},
},
{
name: "throws SellOrderNotFoundError",
transition: "FulfillOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 999],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.SellOrderNotFoundError,
want: undefined,
},
{
name: "throws BuyOrderNotFoundError",
transition: "FulfillOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 999],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
dest: ["ByStr20", getTestAddr(SELLER)],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.BuyOrderNotFoundError,
want: undefined,
},
{
name: "throws ExpiredError",
transition: "FulfillOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: async () => {
await increaseBNum(zilliqa, 5);
},
error: FIXED_PRICE_ERROR.ExpiredError,
want: undefined,
},
{
name: "Buyer fullfills sell order",
transition: "FulfillOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(BUYER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 9750,
[getTestAddr(BUYER)]: -10000,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 250,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: -10000,
}),
events: [
{
name: "FulfillOrder",
getParams: () => ({
taker: ["ByStr20", getTestAddr(BUYER)],
side: ["Uint32", 0],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
seller: ["ByStr20", getTestAddr(SELLER)],
buyer: ["ByStr20", getTestAddr(BUYER)],
asset_recipient: ["ByStr20", getTestAddr(BUYER)],
payment_tokens_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_amount: ["Uint128", 1000],
service_fee: ["Uint128", 250],
}),
},
// royalty fee
{
name: "TransferFromSuccess",
getParams: () => ({
initiator: ["ByStr20", globalMarketplaceAddress],
sender: ["ByStr20", getTestAddr(BUYER)],
recipient: ["ByStr20", getTestAddr(SELLER)],
amount: ["Uint128", 1000],
}),
},
// service fee
{
name: "TransferFromSuccess",
getParams: () => ({
initiator: ["ByStr20", globalMarketplaceAddress],
sender: ["ByStr20", getTestAddr(BUYER)],
recipient: ["ByStr20", getTestAddr(MARKETPLACE_CONTRACT_OWNER)],
amount: ["Uint128", 250],
}),
},
// seller profit
{
name: "TransferFromSuccess",
getParams: () => ({
initiator: ["ByStr20", globalMarketplaceAddress],
sender: ["ByStr20", getTestAddr(BUYER)],
recipient: ["ByStr20", getTestAddr(SELLER)],
amount: ["Uint128", 8750],
}),
},
// NFT transfer
{
name: "TransferFrom",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.sell_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {},
})
);
},
},
},
{
name: "Seller fullfills buy order",
transition: "FulfillOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
dest: ["ByStr20", getTestAddr(BUYER)],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(BUYER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 1000 + 8750,
[getTestAddr(BUYER)]: -10000,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 250,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: -10000,
}),
events: [
{
name: "FulfillOrder",
getParams: () => ({
taker: ["ByStr20", getTestAddr(SELLER)],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
seller: ["ByStr20", getTestAddr(SELLER)],
buyer: ["ByStr20", getTestAddr(BUYER)],
asset_recipient: ["ByStr20", getTestAddr(BUYER)],
payment_tokens_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_recipient: ["ByStr20", getTestAddr(SELLER)],
royalty_amount: ["Uint128", 1000],
service_fee: ["Uint128", 250],
}),
},
// royalty fee
{
name: "TransferFromSuccess",
getParams: () => ({
initiator: ["ByStr20", globalMarketplaceAddress],
sender: ["ByStr20", getTestAddr(BUYER)],
recipient: ["ByStr20", getTestAddr(SELLER)],
amount: ["Uint128", 1000],
}),
},
// service fee
{
name: "TransferFromSuccess",
getParams: () => ({
initiator: ["ByStr20", globalMarketplaceAddress],
sender: ["ByStr20", getTestAddr(BUYER)],
recipient: ["ByStr20", getTestAddr(MARKETPLACE_CONTRACT_OWNER)],
amount: ["Uint128", 250],
}),
},
// seller profit
{
name: "TransferFromSuccess",
getParams: () => ({
initiator: ["ByStr20", globalMarketplaceAddress],
sender: ["ByStr20", getTestAddr(BUYER)],
recipient: ["ByStr20", getTestAddr(SELLER)],
amount: ["Uint128", 8750],
}),
},
// NFT transfer
{
name: "TransferFrom",
getParams: () => ({
from: ["ByStr20", getTestAddr(SELLER)],
to: ["ByStr20", getTestAddr(BUYER)],
token_id: ["Uint256", 1],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: { [globalPaymentTokenAddress.toLowerCase()]: {} },
},
})
);
},
},
},
{
name: "throws NotAllowedToCancelOrder by stranger",
transition: "CancelOrder",
getSender: () => getTestAddr(STRANGER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
}),
beforeTransition: asyncNoop,
error: FIXED_PRICE_ERROR.NotAllowedToCancelOrder,
},
{
name: "Buyer cancels buy order",
transition: "CancelOrder",
getSender: () => getTestAddr(BUYER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 1],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: 0,
}),
events: [
{
name: "CancelOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(BUYER)],
side: ["Uint32", 1],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.buy_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: { [globalPaymentTokenAddress.toLowerCase()]: {} },
},
})
);
},
},
},
{
name: "Seller cancels sell order",
transition: "CancelOrder",
getSender: () => getTestAddr(SELLER),
getParams: () => ({
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
side: ["Uint32", 0],
}),
beforeTransition: asyncNoop,
error: undefined,
want: {
getTokenOwners: () => ({
1: getTestAddr(SELLER),
2: getTestAddr(SELLER),
3: getTestAddr(SELLER),
}),
getBalanceDeltas: () => ({
[globalMarketplaceAddress]: 0,
[getTestAddr(SELLER)]: 0,
[getTestAddr(BUYER)]: 0,
[getTestAddr(MARKETPLACE_CONTRACT_OWNER)]: 0,
[getTestAddr(STRANGER)]: 0,
}),
getAllowanceDeltas: () => ({
[[getTestAddr(BUYER), globalMarketplaceAddress].join()]: 0,
}),
events: [
{
name: "CancelOrder",
getParams: () => ({
maker: ["ByStr20", getTestAddr(SELLER)],
side: ["Uint32", 0],
token_address: ["ByStr20", globalTokenAddress],
token_id: ["Uint256", 1],
payment_token_address: ["ByStr20", globalPaymentTokenAddress],
sale_price: ["Uint128", 10000],
}),
},
],
expectState: (state) => {
expect(JSON.stringify(state.sell_orders)).toBe(
JSON.stringify({
[globalTokenAddress.toLowerCase()]: {
[1]: { [globalPaymentTokenAddress.toLowerCase()]: {} },
},
})
);
},
},
},
];
for (const testCase of testCases) {
it(`${testCase.transition}: ${testCase.name}`, async () => {
await testCase.beforeTransition();
let balanceTracker;
if (testCase.want) {
balanceTracker = new BalanceTracker(
zilliqa,
Object.keys(await testCase.want.getBalanceDeltas()),
zrc2BalancesGetter(zilliqa, globalPaymentTokenAddress)
);
await balanceTracker.init();
}
let allowanceTracker;
if (testCase.want) {
allowanceTracker = new BalanceTracker(
zilliqa,
Object.keys(await testCase.want.getAllowanceDeltas()),
zrc2AllowncesGetter(zilliqa, globalPaymentTokenAddress)
);
await allowanceTracker.init();
}
zilliqa.wallet.setDefault(testCase.getSender());
const tx: any = await zilliqa.contracts
.at(globalMarketplaceAddress)
.call(testCase.transition, scillaJSONParams(testCase.getParams()), {
...TX_PARAMS,
});
if (testCase.want === undefined) {
// Negative Cases
expect(tx.receipt.success).toBe(false);
expect(tx.receipt.exceptions[0].message).toBe(
getErrorMsg(testCase.error)
);
} else {
// Positive Cases
expect(tx.receipt.success).toBe(true);
expectEvents(tx.receipt.event_logs, testCase.want.events);
testCase.want.expectState(
await zilliqa.contracts.at(globalMarketplaceAddress).getState()
);
expectTokenOwners(
(await zilliqa.contracts.at(globalTokenAddress).getState())[
"token_owners"
],
testCase.want.getTokenOwners()
);
expectDeltas(
await balanceTracker.deltas(),
await testCase.want?.getBalanceDeltas()
);
expectDeltas(
await allowanceTracker.deltas(),
await testCase.want?.getAllowanceDeltas()
);
}
});
}
});