@0x/contracts-exchange
: Fully incorporate ERC1155 and MultiAsset tests into the fillOrder
and matchOrders
test suites.
This commit is contained in:
parent
57ac0ca6e8
commit
07e3ba014c
@ -29,6 +29,14 @@
|
||||
{
|
||||
"note": "Add support for `SignatureType.WalletOrderValidator` for orders",
|
||||
"pr": 1774
|
||||
},
|
||||
{
|
||||
"note": "Remove ZRX fees in lieu of arbitrary maker and taker fee tokens.",
|
||||
"pr": 1819
|
||||
},
|
||||
{
|
||||
"note": "Incorporate Multi-asset and ERC1155 tests into `fillOrder` and `matchOrders` tests",
|
||||
"pr": 1819
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -177,6 +177,24 @@ describe('FillOrder Tests', () => {
|
||||
});
|
||||
|
||||
describe('ERC20', () => {
|
||||
const assetCombinations = getAllPossiblePairs([
|
||||
AssetDataScenario.ERC20ZeroDecimals,
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20EighteenDecimals,
|
||||
]);
|
||||
for (const [makerAsset, takerAsset] of assetCombinations) {
|
||||
it(`should transfer correct amounts between ${makerAsset} and ${takerAsset}`, async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: makerAsset,
|
||||
takerAssetDataScenario: takerAsset,
|
||||
},
|
||||
};
|
||||
await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario);
|
||||
});
|
||||
}
|
||||
it('should be able to pay maker fee with taker asset', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
@ -573,14 +591,14 @@ describe('FillOrder Tests', () => {
|
||||
AssetDataScenario.ERC1155NonFungible,
|
||||
AssetDataScenario.MultiAssetERC20,
|
||||
];
|
||||
for (const [makerAssetData, takerAssetData] of getAllPossiblePairs(assetDataScenarios)) {
|
||||
it(`should successfully exchange ${makerAssetData} for ${takerAssetData}`, async () => {
|
||||
for (const [makerAsset, takerAsset] of getAllPossiblePairs(assetDataScenarios)) {
|
||||
it(`should successfully exchange ${makerAsset} for ${takerAsset}`, async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: makerAssetData,
|
||||
takerAssetDataScenario: takerAssetData,
|
||||
makerAssetDataScenario: makerAsset,
|
||||
takerAssetDataScenario: takerAsset,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount,
|
||||
};
|
||||
@ -590,21 +608,21 @@ describe('FillOrder Tests', () => {
|
||||
});
|
||||
|
||||
describe('Maker/taker fee asset combinations', () => {
|
||||
const feeAssetDataScenarios = [
|
||||
const feeAssetPairs = getAllPossiblePairs([
|
||||
FeeAssetDataScenario.ERC20EighteenDecimals,
|
||||
FeeAssetDataScenario.ERC721,
|
||||
FeeAssetDataScenario.ERC1155Fungible,
|
||||
FeeAssetDataScenario.ERC1155NonFungible,
|
||||
FeeAssetDataScenario.MultiAssetERC20,
|
||||
];
|
||||
for (const [makerFeeAssetData, takerFeeAssetData] of getAllPossiblePairs(feeAssetDataScenarios)) {
|
||||
it(`should successfully pay maker fee ${makerFeeAssetData} and taker fee ${takerFeeAssetData}`, async () => {
|
||||
]);
|
||||
for (const [makerFeeAsset, takerFeeAsset] of feeAssetPairs) {
|
||||
it(`should successfully pay maker fee ${makerFeeAsset} and taker fee ${takerFeeAsset}`, async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerFeeAssetDataScenario: makerFeeAssetData,
|
||||
takerFeeAssetDataScenario: takerFeeAssetData,
|
||||
makerFeeAssetDataScenario: makerFeeAsset,
|
||||
takerFeeAssetDataScenario: takerFeeAsset,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount,
|
||||
};
|
||||
|
@ -14,8 +14,6 @@ import { DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import {
|
||||
chaiSetup,
|
||||
constants,
|
||||
ERC1155HoldingsByOwner,
|
||||
ERC721TokenIdsByOwner,
|
||||
OrderFactory,
|
||||
provider,
|
||||
txDefaults,
|
||||
@ -34,17 +32,14 @@ import {
|
||||
constants as exchangeConstants,
|
||||
ExchangeContract,
|
||||
ExchangeWrapper,
|
||||
MatchOrderTester,
|
||||
ReentrantERC20TokenContract,
|
||||
TestExchangeInternalsContract,
|
||||
} from '../src';
|
||||
|
||||
interface IndividualERC1155Holdings {
|
||||
fungible: {
|
||||
[tokenId: string]: BigNumber;
|
||||
};
|
||||
nonFungible: BigNumber[];
|
||||
}
|
||||
import { MatchOrderTester, TokenBalances } from './utils/match_order_tester';
|
||||
|
||||
const ONE = new BigNumber(1);
|
||||
const TWO = new BigNumber(2);
|
||||
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
chaiSetup.configure();
|
||||
@ -76,14 +71,12 @@ describe('matchOrders', () => {
|
||||
let orderFactoryLeft: OrderFactory;
|
||||
let orderFactoryRight: OrderFactory;
|
||||
|
||||
let erc721LeftMakerAssetIds: BigNumber[];
|
||||
let erc721RightMakerAssetIds: BigNumber[];
|
||||
let erc1155LeftMakerHoldings: IndividualERC1155Holdings;
|
||||
let erc1155RightMakerHoldings: IndividualERC1155Holdings;
|
||||
let tokenBalances: TokenBalances;
|
||||
|
||||
let defaultERC20MakerAssetAddress: string;
|
||||
let defaultERC20TakerAssetAddress: string;
|
||||
let defaultERC721AssetAddress: string;
|
||||
let defaultERC1155AssetAddress: string;
|
||||
let defaultFeeTokenAddress: string;
|
||||
|
||||
let matchOrderTester: MatchOrderTester;
|
||||
@ -109,41 +102,25 @@ describe('matchOrders', () => {
|
||||
feeRecipientAddressLeft,
|
||||
feeRecipientAddressRight,
|
||||
] = accounts);
|
||||
const addressesWithBalances = usedAddresses.slice(1);
|
||||
// Create wrappers
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner);
|
||||
erc20Wrapper = new ERC20Wrapper(provider, addressesWithBalances, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, addressesWithBalances, owner);
|
||||
erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, addressesWithBalances, owner);
|
||||
// Deploy ERC20 token & ERC20 proxy
|
||||
const numDummyErc20ToDeploy = 4;
|
||||
erc20Tokens = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Tokens = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// Deploy ERC721 token and proxy
|
||||
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
|
||||
erc721Proxy = await erc721Wrapper.deployProxyAsync();
|
||||
await erc721Wrapper.setBalancesAndAllowancesAsync();
|
||||
const erc721Balances = await erc721Wrapper.getBalancesAsync();
|
||||
erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address];
|
||||
erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address];
|
||||
// Deploy ERC1155 token and proxy
|
||||
[erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyContractsAsync();
|
||||
erc1155Token = erc1155Wrapper.getContract();
|
||||
erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync();
|
||||
await erc1155ProxyWrapper.setBalancesAndAllowancesAsync();
|
||||
const erc1155Holdings = await erc1155ProxyWrapper.getBalancesAsync();
|
||||
erc1155LeftMakerHoldings = getIndividualERC1155Holdings(
|
||||
erc1155Holdings,
|
||||
erc1155Token.address,
|
||||
makerAddressLeft,
|
||||
);
|
||||
erc1155RightMakerHoldings = getIndividualERC1155Holdings(
|
||||
erc1155Holdings,
|
||||
erc1155Token.address,
|
||||
makerAddressRight,
|
||||
);
|
||||
// Deploy MultiAssetProxy.
|
||||
const multiAssetProxyContract = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
|
||||
assetProxyArtifacts.MultiAssetProxy,
|
||||
@ -178,6 +155,11 @@ describe('matchOrders', () => {
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await multiAssetProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
exchange.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
multiAssetProxyContract.address,
|
||||
{ from: owner },
|
||||
@ -193,8 +175,18 @@ describe('matchOrders', () => {
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await multiAssetProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
exchange.address,
|
||||
await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
|
||||
erc721Proxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
|
||||
erc1155Proxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
@ -211,6 +203,7 @@ describe('matchOrders', () => {
|
||||
defaultERC20TakerAssetAddress = erc20Tokens[1].address;
|
||||
defaultFeeTokenAddress = erc20Tokens[2].address;
|
||||
defaultERC721AssetAddress = erc721Token.address;
|
||||
defaultERC1155AssetAddress = erc1155Token.address;
|
||||
const domain = {
|
||||
verifyingContractAddress: exchange.address,
|
||||
chainId,
|
||||
@ -240,17 +233,15 @@ describe('matchOrders', () => {
|
||||
orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
|
||||
const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
|
||||
orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
|
||||
// Set match order tester
|
||||
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper);
|
||||
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeInternals,
|
||||
provider,
|
||||
txDefaults,
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
|
||||
console.log(erc1155LeftMakerHoldings);
|
||||
console.log(erc1155RightMakerHoldings);
|
||||
// Create match order tester
|
||||
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, erc1155ProxyWrapper);
|
||||
tokenBalances = await matchOrderTester.getBalancesAsync();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@ -1266,267 +1257,452 @@ describe('matchOrders', () => {
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => {
|
||||
// Create orders to match
|
||||
const erc721TokenToTransfer = erc721LeftMakerAssetIds[0];
|
||||
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
|
||||
makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
});
|
||||
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
|
||||
takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
});
|
||||
// Match orders
|
||||
const expectedTransferAmounts = {
|
||||
// Left Maker
|
||||
leftMakerAssetSoldByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(1, 0),
|
||||
leftMakerFeeAssetPaidByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
// Right Maker
|
||||
rightMakerAssetSoldByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
rightMakerFeeAssetPaidByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
// Taker
|
||||
leftTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
rightTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 50%
|
||||
};
|
||||
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
|
||||
{
|
||||
leftOrder: signedOrderLeft,
|
||||
rightOrder: signedOrderRight,
|
||||
},
|
||||
takerAddress,
|
||||
expectedTransferAmounts,
|
||||
);
|
||||
});
|
||||
|
||||
it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => {
|
||||
// Create orders to match
|
||||
const erc721TokenToTransfer = erc721RightMakerAssetIds[0];
|
||||
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
|
||||
takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
});
|
||||
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
|
||||
makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(8, 18),
|
||||
});
|
||||
// Match orders
|
||||
const expectedTransferAmounts = {
|
||||
// Left Maker
|
||||
leftMakerAssetSoldByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
|
||||
leftMakerFeeAssetPaidByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
// Right Maker
|
||||
rightMakerAssetSoldByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(1, 0),
|
||||
leftMakerAssetBoughtByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(8, 18),
|
||||
rightMakerFeeAssetPaidByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
// Taker
|
||||
leftMakerAssetReceivedByTakerAmount: Web3Wrapper.toBaseUnitAmount(2, 18),
|
||||
leftTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
rightTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
|
||||
};
|
||||
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
|
||||
{
|
||||
leftOrder: signedOrderLeft,
|
||||
rightOrder: signedOrderRight,
|
||||
},
|
||||
takerAddress,
|
||||
expectedTransferAmounts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('fee tokens', () => {
|
||||
describe('combinations', () => {
|
||||
// tslint:disable: enum-naming
|
||||
enum TokenType {
|
||||
ERC20A = 'ERC20A',
|
||||
ERC20B = 'ERC20B',
|
||||
ERC20C = 'ERC20C',
|
||||
ERC20D = 'ERC20D',
|
||||
ERC721A = 'ERC721A',
|
||||
ERC721B = 'ERC721B',
|
||||
ERC721C = 'ERC721C',
|
||||
ERC721D = 'ERC721D',
|
||||
enum AssetType {
|
||||
ERC20A = 'ERC20_A',
|
||||
ERC20B = 'ERC20_B',
|
||||
ERC20C = 'ERC20_C',
|
||||
ERC20D = 'ERC20_D',
|
||||
ERC721LeftMaker = 'ERC721_LEFT_MAKER',
|
||||
ERC721RightMaker = 'ERC721_RIGHT_MAKER',
|
||||
ERC721Taker = 'ERC721_TAKER',
|
||||
ERC1155FungibleA = 'ERC1155_FUNGIBLE_A',
|
||||
ERC1155FungibleB = 'ERC1155_FUNGIBLE_B',
|
||||
ERC1155FungibleC = 'ERC1155_FUNGIBLE_C',
|
||||
ERC1155FungibleD = 'ERC1155_FUNGIBLE_D',
|
||||
ERC1155NonFungibleLeftMaker = 'ERC1155_NON_FUNGIBLE_LEFT_MAKER',
|
||||
ERC1155NonFungibleRightMaker = 'ERC1155_NON_FUNGIBLE_RIGHT_MAKER',
|
||||
ERC1155NonFungibleTaker = 'ERC1155_NON_FUNGIBLE_TAKER',
|
||||
MultiAssetA = 'MULTI_ASSET_A',
|
||||
MultiAssetB = 'MULTI_ASSET_B',
|
||||
MultiAssetC = 'MULTI_ASSET_C',
|
||||
MultiAssetD = 'MULTI_ASSET_D',
|
||||
}
|
||||
const fungibleTypes = [
|
||||
AssetType.ERC20A,
|
||||
AssetType.ERC20B,
|
||||
AssetType.ERC20C,
|
||||
AssetType.ERC20D,
|
||||
AssetType.ERC1155FungibleA,
|
||||
AssetType.ERC1155FungibleB,
|
||||
AssetType.ERC1155FungibleC,
|
||||
AssetType.ERC1155FungibleD,
|
||||
AssetType.MultiAssetA,
|
||||
AssetType.MultiAssetB,
|
||||
AssetType.MultiAssetC,
|
||||
AssetType.MultiAssetD,
|
||||
];
|
||||
interface AssetCombination {
|
||||
leftMaker: TokenType;
|
||||
rightMaker: TokenType;
|
||||
leftMakerFee: TokenType;
|
||||
rightMakerFee: TokenType;
|
||||
leftTakerFee: TokenType;
|
||||
rightTakerFee: TokenType;
|
||||
leftMaker: AssetType;
|
||||
rightMaker: AssetType;
|
||||
leftMakerFee: AssetType;
|
||||
rightMakerFee: AssetType;
|
||||
leftTakerFee: AssetType;
|
||||
rightTakerFee: AssetType;
|
||||
description?: string;
|
||||
shouldFail?: boolean;
|
||||
}
|
||||
const feeAssetCombinations: AssetCombination[] = [
|
||||
const assetCombinations: AssetCombination[] = [
|
||||
{
|
||||
description: 'Swapping tokens then using them to pay maker fees.',
|
||||
leftMaker: TokenType.ERC20A,
|
||||
rightMaker: TokenType.ERC20B,
|
||||
leftMakerFee: TokenType.ERC20B,
|
||||
rightMakerFee: TokenType.ERC20A,
|
||||
leftTakerFee: TokenType.ERC20C,
|
||||
rightTakerFee: TokenType.ERC20C,
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC20B,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Swapping tokens then using them to pay taker fees.',
|
||||
leftMaker: TokenType.ERC20A,
|
||||
rightMaker: TokenType.ERC20B,
|
||||
leftMakerFee: TokenType.ERC20C,
|
||||
rightMakerFee: TokenType.ERC20C,
|
||||
leftTakerFee: TokenType.ERC20B,
|
||||
rightTakerFee: TokenType.ERC20A,
|
||||
leftMaker: AssetType.ERC721LeftMaker,
|
||||
rightMaker: AssetType.ERC721RightMaker,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Swapping tokens then using them to pay maker and taker fees.',
|
||||
leftMaker: TokenType.ERC20A,
|
||||
rightMaker: TokenType.ERC20B,
|
||||
leftMakerFee: TokenType.ERC20B,
|
||||
rightMakerFee: TokenType.ERC20A,
|
||||
leftTakerFee: TokenType.ERC20C,
|
||||
rightTakerFee: TokenType.ERC20C,
|
||||
leftMaker: AssetType.ERC721LeftMaker,
|
||||
rightMaker: AssetType.ERC20A,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Paying maker and taker fees with same tokens being sold.',
|
||||
leftMaker: TokenType.ERC20A,
|
||||
rightMaker: TokenType.ERC20B,
|
||||
leftMakerFee: TokenType.ERC20A,
|
||||
rightMakerFee: TokenType.ERC20B,
|
||||
leftTakerFee: TokenType.ERC20A,
|
||||
rightTakerFee: TokenType.ERC20B,
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC721RightMaker,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Paying maker and taker fees with same tokens being bought.',
|
||||
leftMaker: TokenType.ERC20A,
|
||||
rightMaker: TokenType.ERC20B,
|
||||
leftMakerFee: TokenType.ERC20B,
|
||||
rightMakerFee: TokenType.ERC20A,
|
||||
leftTakerFee: TokenType.ERC20B,
|
||||
rightTakerFee: TokenType.ERC20A,
|
||||
leftMaker: AssetType.ERC1155FungibleA,
|
||||
rightMaker: AssetType.ERC20A,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Buy an ERC721 then use it to pay maker fees.',
|
||||
leftMaker: TokenType.ERC20A,
|
||||
rightMaker: TokenType.ERC721A,
|
||||
leftMakerFee: TokenType.ERC721A,
|
||||
rightMakerFee: TokenType.ERC20B,
|
||||
leftTakerFee: TokenType.ERC20C,
|
||||
rightTakerFee: TokenType.ERC20C,
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC1155FungibleB,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Buy an ERC721 then use it to pay maker fee (the other way).',
|
||||
leftMaker: TokenType.ERC721A,
|
||||
rightMaker: TokenType.ERC20A,
|
||||
leftMakerFee: TokenType.ERC20B,
|
||||
rightMakerFee: TokenType.ERC721A,
|
||||
leftTakerFee: TokenType.ERC20C,
|
||||
rightTakerFee: TokenType.ERC20C,
|
||||
leftMaker: AssetType.ERC1155FungibleA,
|
||||
rightMaker: AssetType.ERC1155FungibleA,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC1155NonFungibleLeftMaker,
|
||||
rightMaker: AssetType.ERC20A,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC1155NonFungibleRightMaker,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC1155NonFungibleLeftMaker,
|
||||
rightMaker: AssetType.ERC1155NonFungibleRightMaker,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC1155FungibleA,
|
||||
rightMaker: AssetType.ERC20A,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC1155FungibleB,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC1155FungibleB,
|
||||
rightMaker: AssetType.ERC1155FungibleB,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.MultiAssetA,
|
||||
rightMaker: AssetType.ERC20A,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.MultiAssetB,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.MultiAssetA,
|
||||
rightMaker: AssetType.MultiAssetB,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC20C,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
leftMaker: AssetType.MultiAssetA,
|
||||
rightMaker: AssetType.ERC1155FungibleA,
|
||||
leftMakerFee: AssetType.ERC1155FungibleA,
|
||||
rightMakerFee: AssetType.MultiAssetA,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
},
|
||||
{
|
||||
description: 'Paying maker fees with the same ERC20 tokens being bought.',
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC20B,
|
||||
leftMakerFee: AssetType.ERC20B,
|
||||
rightMakerFee: AssetType.ERC20A,
|
||||
leftTakerFee: AssetType.ERC20B,
|
||||
rightTakerFee: AssetType.ERC20A,
|
||||
},
|
||||
{
|
||||
description: 'Paying maker fees with the same ERC20 tokens being sold.',
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC20B,
|
||||
leftMakerFee: AssetType.ERC20A,
|
||||
rightMakerFee: AssetType.ERC20B,
|
||||
leftTakerFee: AssetType.ERC20A,
|
||||
rightTakerFee: AssetType.ERC20B,
|
||||
},
|
||||
{
|
||||
description: 'Using all the same ERC20 asset.',
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC20A,
|
||||
leftMakerFee: AssetType.ERC20A,
|
||||
rightMakerFee: AssetType.ERC20A,
|
||||
leftTakerFee: AssetType.ERC20A,
|
||||
rightTakerFee: AssetType.ERC20A,
|
||||
},
|
||||
{
|
||||
description: 'Paying fees with the same MAP assets being sold.',
|
||||
leftMaker: AssetType.MultiAssetA,
|
||||
rightMaker: AssetType.MultiAssetB,
|
||||
leftMakerFee: AssetType.MultiAssetA,
|
||||
rightMakerFee: AssetType.MultiAssetB,
|
||||
leftTakerFee: AssetType.MultiAssetA,
|
||||
rightTakerFee: AssetType.MultiAssetB,
|
||||
},
|
||||
{
|
||||
description: 'Paying fees with the same MAP assets being bought.',
|
||||
leftMaker: AssetType.MultiAssetA,
|
||||
rightMaker: AssetType.MultiAssetB,
|
||||
leftMakerFee: AssetType.MultiAssetB,
|
||||
rightMakerFee: AssetType.MultiAssetA,
|
||||
leftTakerFee: AssetType.MultiAssetB,
|
||||
rightTakerFee: AssetType.MultiAssetA,
|
||||
},
|
||||
{
|
||||
description: 'Using all the same MAP assets.',
|
||||
leftMaker: AssetType.MultiAssetA,
|
||||
rightMaker: AssetType.MultiAssetA,
|
||||
leftMakerFee: AssetType.MultiAssetA,
|
||||
rightMakerFee: AssetType.MultiAssetA,
|
||||
leftTakerFee: AssetType.MultiAssetA,
|
||||
rightTakerFee: AssetType.MultiAssetA,
|
||||
},
|
||||
{
|
||||
description: 'Swapping ERC721s then using them to pay maker fees.',
|
||||
leftMaker: TokenType.ERC721A,
|
||||
rightMaker: TokenType.ERC721B,
|
||||
leftMakerFee: TokenType.ERC721B,
|
||||
rightMakerFee: TokenType.ERC721A,
|
||||
leftTakerFee: TokenType.ERC20A,
|
||||
rightTakerFee: TokenType.ERC20A,
|
||||
leftMaker: AssetType.ERC721LeftMaker,
|
||||
rightMaker: AssetType.ERC721RightMaker,
|
||||
leftMakerFee: AssetType.ERC721RightMaker,
|
||||
rightMakerFee: AssetType.ERC721LeftMaker,
|
||||
leftTakerFee: AssetType.ERC20A,
|
||||
rightTakerFee: AssetType.ERC20A,
|
||||
},
|
||||
{
|
||||
description: 'Swapping ERC1155 NFTs then using them to pay maker fees.',
|
||||
leftMaker: AssetType.ERC1155NonFungibleLeftMaker,
|
||||
rightMaker: AssetType.ERC1155NonFungibleRightMaker,
|
||||
leftMakerFee: AssetType.ERC1155NonFungibleRightMaker,
|
||||
rightMakerFee: AssetType.ERC1155NonFungibleLeftMaker,
|
||||
leftTakerFee: AssetType.ERC20A,
|
||||
rightTakerFee: AssetType.ERC20A,
|
||||
},
|
||||
{
|
||||
description: 'Double-spend by trying to pay maker fees with sold ERC721 token (fail).',
|
||||
leftMaker: TokenType.ERC721A,
|
||||
rightMaker: TokenType.ERC721B,
|
||||
leftMakerFee: TokenType.ERC721A,
|
||||
rightMakerFee: TokenType.ERC721A,
|
||||
leftTakerFee: TokenType.ERC20A,
|
||||
rightTakerFee: TokenType.ERC20A,
|
||||
leftMaker: AssetType.ERC721LeftMaker,
|
||||
rightMaker: AssetType.ERC721RightMaker,
|
||||
leftMakerFee: AssetType.ERC721LeftMaker,
|
||||
rightMakerFee: AssetType.ERC721LeftMaker,
|
||||
leftTakerFee: AssetType.ERC20A,
|
||||
rightTakerFee: AssetType.ERC20A,
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
description: 'Double-spend by trying to pay maker fees with sold ERC1155 NFT (fail).',
|
||||
leftMaker: AssetType.ERC20A,
|
||||
rightMaker: AssetType.ERC1155NonFungibleLeftMaker,
|
||||
leftMakerFee: AssetType.ERC20C,
|
||||
rightMakerFee: AssetType.ERC1155NonFungibleLeftMaker,
|
||||
leftTakerFee: AssetType.ERC20C,
|
||||
rightTakerFee: AssetType.ERC20C,
|
||||
shouldFail: true,
|
||||
},
|
||||
];
|
||||
|
||||
let erc721TokenIdsByOwner: ERC721TokenIdsByOwner;
|
||||
let nameToERC20Tokens: { [name: string]: string };
|
||||
let nameToERC721Tokens: { [name: string]: string };
|
||||
let nameToERC20Asset: { [name: string]: string };
|
||||
let nameToERC721Asset: { [name: string]: [string, BigNumber] };
|
||||
let nameToERC1155FungibleAsset: { [name: string]: [string, BigNumber] };
|
||||
let nameToERC1155NonFungibleAsset: { [name: string]: [string, BigNumber] };
|
||||
let nameToMultiAssetAsset: { [name: string]: [BigNumber[], string[]] };
|
||||
|
||||
function getAssetData(tokenType: TokenType, ownerAddress: string): string {
|
||||
function getAssetData(assetType: AssetType): string {
|
||||
const encodeERC20AssetData = assetDataUtils.encodeERC20AssetData;
|
||||
const encodeERC721AssetData = assetDataUtils.encodeERC721AssetData;
|
||||
if (nameToERC20Tokens[tokenType] !== undefined) {
|
||||
const tokenAddress = nameToERC20Tokens[tokenType];
|
||||
const encodeERC1155AssetData = assetDataUtils.encodeERC1155AssetData;
|
||||
const encodeMultiAssetData = assetDataUtils.encodeMultiAssetData;
|
||||
if (nameToERC20Asset[assetType] !== undefined) {
|
||||
const tokenAddress = nameToERC20Asset[assetType];
|
||||
return encodeERC20AssetData(tokenAddress);
|
||||
}
|
||||
if (nameToERC721Tokens[tokenType] !== undefined) {
|
||||
const tokenAddress = nameToERC721Tokens[tokenType];
|
||||
const tokenIdx = tokenType.charCodeAt(tokenType.length - 1) - 'A'.charCodeAt(0);
|
||||
const tokenId = erc721TokenIdsByOwner[ownerAddress][tokenAddress][tokenIdx];
|
||||
return encodeERC721AssetData(nameToERC721Tokens[tokenType], tokenId);
|
||||
if (nameToERC721Asset[assetType] !== undefined) {
|
||||
const [tokenAddress, tokenId] = nameToERC721Asset[assetType];
|
||||
return encodeERC721AssetData(tokenAddress, tokenId);
|
||||
}
|
||||
return '0x';
|
||||
if (nameToERC1155FungibleAsset[assetType] !== undefined) {
|
||||
const [tokenAddress, tokenId] = nameToERC1155FungibleAsset[assetType];
|
||||
return encodeERC1155AssetData(tokenAddress, [tokenId], [ONE], constants.NULL_BYTES);
|
||||
}
|
||||
if (nameToERC1155NonFungibleAsset[assetType] !== undefined) {
|
||||
const [tokenAddress, tokenId] = nameToERC1155NonFungibleAsset[assetType];
|
||||
return encodeERC1155AssetData(tokenAddress, [tokenId], [ONE], constants.NULL_BYTES);
|
||||
}
|
||||
if (nameToMultiAssetAsset[assetType] !== undefined) {
|
||||
const [amounts, nestedAssetData] = nameToMultiAssetAsset[assetType];
|
||||
return encodeMultiAssetData(amounts, nestedAssetData);
|
||||
}
|
||||
throw new Error(`Unknown asset type: ${assetType}`);
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync();
|
||||
nameToERC20Tokens = {
|
||||
ERC20A: erc20Tokens[0].address,
|
||||
ERC20B: erc20Tokens[1].address,
|
||||
ERC20C: erc20Tokens[0].address,
|
||||
ERC20D: erc20Tokens[1].address,
|
||||
nameToERC20Asset = {
|
||||
ERC20_A: erc20Tokens[0].address,
|
||||
ERC20_B: erc20Tokens[1].address,
|
||||
ERC20_C: erc20Tokens[2].address,
|
||||
ERC20_D: erc20Tokens[3].address,
|
||||
};
|
||||
nameToERC721Tokens = {
|
||||
ERC721A: erc721Token.address,
|
||||
ERC721B: erc721Token.address,
|
||||
ERC721C: erc721Token.address,
|
||||
ERC721D: erc721Token.address,
|
||||
const erc721TokenIds = _.mapValues(tokenBalances.erc721, v => v[defaultERC721AssetAddress.address][0]);
|
||||
nameToERC721Asset = {
|
||||
ERC721_LEFT_MAKER: [defaultERC721AssetAddress.address, erc721TokenIds[makerAddressLeft]],
|
||||
ERC721_RIGHT_MAKER: [defaultERC721AssetAddress.address, erc721TokenIds[makerAddressRight]],
|
||||
ERC721_TAKER: [defaultERC721AssetAddress.address, erc721TokenIds[takerAddress]],
|
||||
};
|
||||
const erc1155FungibleTokens = _.keys(
|
||||
_.values(tokenBalances.erc1155)[0][defaultERC1155AssetAddress].fungible,
|
||||
).map(k => new BigNumber(k));
|
||||
nameToERC1155FungibleAsset = {
|
||||
ERC1155_FUNGIBLE_A: [defaultERC1155AssetAddress, erc1155FungibleTokens[0]],
|
||||
ERC1155_FUNGIBLE_B: [defaultERC1155AssetAddress, erc1155FungibleTokens[1]],
|
||||
ERC1155_FUNGIBLE_C: [defaultERC1155AssetAddress, erc1155FungibleTokens[2]],
|
||||
ERC1155_FUNGIBLE_D: [defaultERC1155AssetAddress, erc1155FungibleTokens[3]],
|
||||
};
|
||||
const erc1155NonFungibleTokenIds = _.mapValues(
|
||||
tokenBalances.erc1155,
|
||||
v => v[defaultERC1155AssetAddress].nonFungible[0],
|
||||
);
|
||||
nameToERC1155NonFungibleAsset = {
|
||||
ERC1155_NON_FUNGIBLE_LEFT_MAKER: [
|
||||
defaultERC1155AssetAddress,
|
||||
erc1155NonFungibleTokenIds[makerAddressLeft],
|
||||
],
|
||||
ERC1155_NON_FUNGIBLE_RIGHT_MAKER: [
|
||||
defaultERC1155AssetAddress,
|
||||
erc1155NonFungibleTokenIds[makerAddressRight],
|
||||
],
|
||||
ERC1155_NON_FUNGIBLE_TAKER: [defaultERC1155AssetAddress, erc1155NonFungibleTokenIds[takerAddress]],
|
||||
};
|
||||
nameToMultiAssetAsset = {
|
||||
MULTI_ASSET_A: [
|
||||
[ONE, TWO],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(erc20Tokens[0].address),
|
||||
assetDataUtils.encodeERC1155AssetData(
|
||||
defaultERC1155AssetAddress,
|
||||
[erc1155FungibleTokens[0]],
|
||||
[ONE],
|
||||
constants.NULL_BYTES,
|
||||
),
|
||||
],
|
||||
],
|
||||
MULTI_ASSET_B: [
|
||||
[ONE, TWO],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(erc20Tokens[1].address),
|
||||
assetDataUtils.encodeERC1155AssetData(
|
||||
defaultERC1155AssetAddress,
|
||||
[erc1155FungibleTokens[1]],
|
||||
[ONE],
|
||||
constants.NULL_BYTES,
|
||||
),
|
||||
],
|
||||
],
|
||||
MULTI_ASSET_C: [
|
||||
[ONE, TWO],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(erc20Tokens[2].address),
|
||||
assetDataUtils.encodeERC1155AssetData(
|
||||
defaultERC1155AssetAddress,
|
||||
[erc1155FungibleTokens[2]],
|
||||
[ONE],
|
||||
constants.NULL_BYTES,
|
||||
),
|
||||
],
|
||||
],
|
||||
MULTI_ASSET_D: [
|
||||
[ONE, TWO],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(erc20Tokens[3].address),
|
||||
assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Token.address,
|
||||
[erc1155FungibleTokens[3]],
|
||||
[ONE],
|
||||
constants.NULL_BYTES,
|
||||
),
|
||||
],
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
for (const combo of feeAssetCombinations) {
|
||||
for (const combo of assetCombinations) {
|
||||
const description = combo.description || JSON.stringify(combo);
|
||||
it(description, async () => {
|
||||
// Create orders to match. For ERC20s, there will be a spread.
|
||||
const leftMakerAssetAmount = combo.leftMaker.startsWith('ERC20')
|
||||
const leftMakerAssetAmount = _.includes(fungibleTypes, combo.leftMaker)
|
||||
? Web3Wrapper.toBaseUnitAmount(15, 18)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const leftTakerAssetAmount = combo.rightMaker.startsWith('ERC20')
|
||||
const leftTakerAssetAmount = _.includes(fungibleTypes, combo.rightMaker)
|
||||
? Web3Wrapper.toBaseUnitAmount(30, 18)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const rightMakerAssetAmount = combo.rightMaker.startsWith('ERC20')
|
||||
const rightMakerAssetAmount = _.includes(fungibleTypes, combo.rightMaker)
|
||||
? Web3Wrapper.toBaseUnitAmount(30, 18)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const rightTakerAssetAmount = combo.leftMaker.startsWith('ERC20')
|
||||
const rightTakerAssetAmount = _.includes(fungibleTypes, combo.leftMaker)
|
||||
? Web3Wrapper.toBaseUnitAmount(14, 18)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const leftMakerFeeAssetAmount = combo.leftMakerFee.startsWith('ERC20')
|
||||
const leftMakerFeeAssetAmount = _.includes(fungibleTypes, combo.leftMakerFee)
|
||||
? Web3Wrapper.toBaseUnitAmount(8, 12)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const rightMakerFeeAssetAmount = combo.rightMakerFee.startsWith('ERC20')
|
||||
const rightMakerFeeAssetAmount = _.includes(fungibleTypes, combo.rightMakerFee)
|
||||
? Web3Wrapper.toBaseUnitAmount(7, 12)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const leftTakerFeeAssetAmount = combo.leftTakerFee.startsWith('ERC20')
|
||||
const leftTakerFeeAssetAmount = _.includes(fungibleTypes, combo.leftTakerFee)
|
||||
? Web3Wrapper.toBaseUnitAmount(6, 12)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const rightTakerFeeAssetAmount = combo.rightTakerFee.startsWith('ERC20')
|
||||
const rightTakerFeeAssetAmount = _.includes(fungibleTypes, combo.rightTakerFee)
|
||||
? Web3Wrapper.toBaseUnitAmount(5, 12)
|
||||
: Web3Wrapper.toBaseUnitAmount(1, 0);
|
||||
const leftMakerAssetReceivedByTakerAmount = combo.leftMaker.startsWith('ERC20')
|
||||
const leftMakerAssetReceivedByTakerAmount = _.includes(fungibleTypes, combo.leftMaker)
|
||||
? leftMakerAssetAmount.minus(rightTakerAssetAmount)
|
||||
: Web3Wrapper.toBaseUnitAmount(0, 0);
|
||||
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
|
||||
makerAssetData: getAssetData(combo.leftMaker, makerAddressLeft),
|
||||
takerAssetData: getAssetData(combo.rightMaker, makerAddressRight),
|
||||
makerFeeAssetData: getAssetData(combo.leftMakerFee, makerAddressLeft),
|
||||
takerFeeAssetData: getAssetData(combo.leftTakerFee, takerAddress),
|
||||
makerAssetData: getAssetData(combo.leftMaker),
|
||||
takerAssetData: getAssetData(combo.rightMaker),
|
||||
makerFeeAssetData: getAssetData(combo.leftMakerFee),
|
||||
takerFeeAssetData: getAssetData(combo.leftTakerFee),
|
||||
makerAssetAmount: leftMakerAssetAmount,
|
||||
takerAssetAmount: leftTakerAssetAmount,
|
||||
makerFee: leftMakerFeeAssetAmount,
|
||||
takerFee: leftTakerFeeAssetAmount,
|
||||
});
|
||||
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
|
||||
makerAssetData: getAssetData(combo.rightMaker, makerAddressRight),
|
||||
takerAssetData: getAssetData(combo.leftMaker, makerAddressLeft),
|
||||
makerFeeAssetData: getAssetData(combo.rightMakerFee, makerAddressRight),
|
||||
takerFeeAssetData: getAssetData(combo.rightTakerFee, takerAddress),
|
||||
makerAssetData: getAssetData(combo.rightMaker),
|
||||
takerAssetData: getAssetData(combo.leftMaker),
|
||||
makerFeeAssetData: getAssetData(combo.rightMakerFee),
|
||||
takerFeeAssetData: getAssetData(combo.rightTakerFee),
|
||||
makerAssetAmount: rightMakerAssetAmount,
|
||||
takerAssetAmount: rightTakerAssetAmount,
|
||||
makerFee: rightMakerFeeAssetAmount,
|
||||
@ -1536,15 +1712,15 @@ describe('matchOrders', () => {
|
||||
const expectedTransferAmounts = {
|
||||
// Left Maker
|
||||
leftMakerAssetSoldByLeftMakerAmount: leftMakerAssetAmount,
|
||||
leftMakerFeeAssetPaidByLeftMakerAmount: leftMakerFeeAssetAmount, // 100%
|
||||
leftMakerFeeAssetPaidByLeftMakerAmount: leftMakerFeeAssetAmount,
|
||||
// Right Maker
|
||||
rightMakerAssetSoldByRightMakerAmount: rightMakerAssetAmount,
|
||||
leftMakerAssetBoughtByRightMakerAmount: rightTakerAssetAmount,
|
||||
rightMakerFeeAssetPaidByRightMakerAmount: rightMakerFeeAssetAmount, // 100%
|
||||
rightMakerFeeAssetPaidByRightMakerAmount: rightMakerFeeAssetAmount,
|
||||
// Taker
|
||||
leftMakerAssetReceivedByTakerAmount,
|
||||
leftTakerFeeAssetPaidByTakerAmount: leftTakerFeeAssetAmount, // 100%
|
||||
rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetAmount, // 100%
|
||||
leftTakerFeeAssetPaidByTakerAmount: leftTakerFeeAssetAmount,
|
||||
rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetAmount,
|
||||
};
|
||||
if (!combo.shouldFail) {
|
||||
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
|
||||
@ -1564,18 +1740,4 @@ describe('matchOrders', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getIndividualERC1155Holdings(
|
||||
erc1155HoldingsByOwner: ERC1155HoldingsByOwner,
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
): IndividualERC1155Holdings {
|
||||
return {
|
||||
fungible: erc1155HoldingsByOwner.fungible[ownerAddress][tokenAddress],
|
||||
nonFungible: _.uniqBy(_.flatten(_.values(
|
||||
erc1155HoldingsByOwner.nonFungible[ownerAddress][tokenAddress])),
|
||||
v => v.toString(10),
|
||||
),
|
||||
};
|
||||
}
|
||||
// tslint:disable-line:max-file-line-count
|
||||
|
@ -96,24 +96,24 @@ export class AssetWrapper {
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
if (!doesTokenExist && desiredBalance.gt(0)) {
|
||||
if (!doesTokenExist && desiredBalance.gte(1)) {
|
||||
await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
|
||||
return;
|
||||
} else if (!doesTokenExist && desiredBalance.lte(0)) {
|
||||
} else if (!doesTokenExist && desiredBalance.lt(1)) {
|
||||
return; // noop
|
||||
}
|
||||
const tokenOwner = await erc721Wrapper.ownerOfAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
if (userAddress !== tokenOwner && desiredBalance.gt(0)) {
|
||||
if (userAddress !== tokenOwner && desiredBalance.gte(1)) {
|
||||
await erc721Wrapper.transferFromAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
tokenOwner,
|
||||
userAddress,
|
||||
);
|
||||
} else if (tokenOwner === userAddress && desiredBalance.lte(0)) {
|
||||
} else if (tokenOwner === userAddress && desiredBalance.lt(1)) {
|
||||
// Burn token
|
||||
await erc721Wrapper.transferFromAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
@ -123,8 +123,8 @@ export class AssetWrapper {
|
||||
);
|
||||
return;
|
||||
} else if (
|
||||
(userAddress !== tokenOwner && desiredBalance.lte(0)) ||
|
||||
(tokenOwner === userAddress && desiredBalance.gt(0))
|
||||
(userAddress !== tokenOwner && desiredBalance.lt(1)) ||
|
||||
(tokenOwner === userAddress && desiredBalance.gte(1))
|
||||
) {
|
||||
return; // noop
|
||||
}
|
||||
@ -168,7 +168,7 @@ export class AssetWrapper {
|
||||
}
|
||||
} else {
|
||||
const nftOwner = await assetWrapper.getOwnerOfAsync(tokenId);
|
||||
if (scaledDesiredBalance.gt(0)) {
|
||||
if (scaledDesiredBalance.gte(1)) {
|
||||
if (nftOwner === userAddress) {
|
||||
// Nothing to do.
|
||||
} else if (nftOwner !== constants.NULL_ADDRESS) {
|
||||
|
@ -254,7 +254,8 @@ export class FillOrderCombinatorialUtils {
|
||||
// ExpirationTimeSecondsScenario.InPast,
|
||||
];
|
||||
const makerAssetDataScenario = [
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
// FeeAssetDataScenario.ERC20ZeroDecimals,
|
||||
// AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20EighteenDecimals,
|
||||
AssetDataScenario.ERC721,
|
||||
AssetDataScenario.ERC1155Fungible,
|
||||
@ -262,7 +263,8 @@ export class FillOrderCombinatorialUtils {
|
||||
AssetDataScenario.MultiAssetERC20,
|
||||
];
|
||||
const takerAssetDataScenario = [
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
// FeeAssetDataScenario.ERC20ZeroDecimals,
|
||||
// AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20EighteenDecimals,
|
||||
AssetDataScenario.ERC721,
|
||||
AssetDataScenario.ERC1155Fungible,
|
||||
@ -270,7 +272,8 @@ export class FillOrderCombinatorialUtils {
|
||||
AssetDataScenario.MultiAssetERC20,
|
||||
];
|
||||
const makerFeeAssetDataScenario = [
|
||||
FeeAssetDataScenario.ERC20FiveDecimals,
|
||||
// FeeAssetDataScenario.ERC20ZeroDecimals,
|
||||
// FeeAssetDataScenario.ERC20FiveDecimals,
|
||||
FeeAssetDataScenario.ERC20EighteenDecimals,
|
||||
FeeAssetDataScenario.ERC721,
|
||||
FeeAssetDataScenario.ERC1155Fungible,
|
||||
@ -280,7 +283,8 @@ export class FillOrderCombinatorialUtils {
|
||||
FeeAssetDataScenario.TakerToken,
|
||||
];
|
||||
const takerFeeAssetDataScenario = [
|
||||
FeeAssetDataScenario.ERC20FiveDecimals,
|
||||
// FeeAssetDataScenario.ERC20ZeroDecimals,
|
||||
// FeeAssetDataScenario.ERC20FiveDecimals,
|
||||
FeeAssetDataScenario.ERC20EighteenDecimals,
|
||||
FeeAssetDataScenario.ERC721,
|
||||
FeeAssetDataScenario.ERC1155Fungible,
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from './exchange_wrapper';
|
||||
export * from './match_order_tester';
|
||||
export * from './exchange_data_encoder';
|
||||
export * from './types';
|
||||
export * from './constants';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { chaiSetup, OrderStatus, TokenBalancesByOwner } from '@0x/contracts-test-utils';
|
||||
import { ERC1155ProxyWrapper, ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { chaiSetup, ERC1155HoldingsByOwner, OrderStatus } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||
import { AssetProxyId, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
@ -14,6 +14,13 @@ const ZERO = new BigNumber(0);
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
export interface IndividualERC1155Holdings {
|
||||
fungible: {
|
||||
[tokenId: string]: BigNumber;
|
||||
};
|
||||
nonFungible: BigNumber[];
|
||||
}
|
||||
|
||||
export interface FillEventArgs {
|
||||
orderHash: string;
|
||||
makerAddress: string;
|
||||
@ -46,7 +53,32 @@ export interface MatchTransferAmounts {
|
||||
export interface MatchResults {
|
||||
orders: MatchedOrders;
|
||||
fills: FillEventArgs[];
|
||||
balances: TokenBalancesByOwner;
|
||||
balances: TokenBalances;
|
||||
}
|
||||
|
||||
export interface ERC1155Holdings {
|
||||
[owner: string]: {
|
||||
[contract: string]: {
|
||||
fungible: {
|
||||
[tokenId: string]: BigNumber;
|
||||
};
|
||||
nonFungible: BigNumber[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface TokenBalances {
|
||||
erc20: {
|
||||
[owner: string]: {
|
||||
[contract: string]: BigNumber;
|
||||
};
|
||||
};
|
||||
erc721: {
|
||||
[owner: string]: {
|
||||
[contract: string]: BigNumber[];
|
||||
};
|
||||
};
|
||||
erc1155: ERC1155Holdings;
|
||||
}
|
||||
|
||||
export interface MatchedOrders {
|
||||
@ -66,14 +98,16 @@ export class MatchOrderTester {
|
||||
public exchangeWrapper: ExchangeWrapper;
|
||||
public erc20Wrapper: ERC20Wrapper;
|
||||
public erc721Wrapper: ERC721Wrapper;
|
||||
public erc1155ProxyWrapper: ERC1155ProxyWrapper;
|
||||
public matchOrdersCallAsync?: MatchOrdersAsyncCall;
|
||||
private readonly _initialTokenBalancesPromise: Promise<TokenBalancesByOwner>;
|
||||
private readonly _initialTokenBalancesPromise: Promise<TokenBalances>;
|
||||
|
||||
/**
|
||||
* Constructs new MatchOrderTester.
|
||||
* @param exchangeWrapper Used to call to the Exchange.
|
||||
* @param erc20Wrapper Used to fetch ERC20 balances.
|
||||
* @param erc721Wrapper Used to fetch ERC721 token owners.
|
||||
* @param erc1155Wrapper Used to fetch ERC1155 token owners.
|
||||
* @param matchOrdersCallAsync Optional, custom caller for
|
||||
* `ExchangeWrapper.matchOrdersAsync()`.
|
||||
*/
|
||||
@ -81,11 +115,13 @@ export class MatchOrderTester {
|
||||
exchangeWrapper: ExchangeWrapper,
|
||||
erc20Wrapper: ERC20Wrapper,
|
||||
erc721Wrapper: ERC721Wrapper,
|
||||
erc1155ProxyWrapper: ERC1155ProxyWrapper,
|
||||
matchOrdersCallAsync?: MatchOrdersAsyncCall,
|
||||
) {
|
||||
this.exchangeWrapper = exchangeWrapper;
|
||||
this.erc20Wrapper = erc20Wrapper;
|
||||
this.erc721Wrapper = erc721Wrapper;
|
||||
this.erc1155ProxyWrapper = erc1155ProxyWrapper;
|
||||
this.matchOrdersCallAsync = matchOrdersCallAsync;
|
||||
this._initialTokenBalancesPromise = this.getBalancesAsync();
|
||||
}
|
||||
@ -103,7 +139,7 @@ export class MatchOrderTester {
|
||||
orders: MatchedOrders,
|
||||
takerAddress: string,
|
||||
expectedTransferAmounts: Partial<MatchTransferAmounts>,
|
||||
initialTokenBalances?: TokenBalancesByOwner,
|
||||
initialTokenBalances?: TokenBalances,
|
||||
): Promise<MatchResults> {
|
||||
await assertInitialOrderStatesAsync(orders, this.exchangeWrapper);
|
||||
// Get the token balances before executing `matchOrders()`.
|
||||
@ -136,8 +172,8 @@ export class MatchOrderTester {
|
||||
/**
|
||||
* Fetch the current token balances of all known accounts.
|
||||
*/
|
||||
public async getBalancesAsync(): Promise<TokenBalancesByOwner> {
|
||||
return getTokenBalancesAsync(this.erc20Wrapper, this.erc721Wrapper);
|
||||
public async getBalancesAsync(): Promise<TokenBalances> {
|
||||
return getTokenBalancesAsync(this.erc20Wrapper, this.erc721Wrapper, this.erc1155ProxyWrapper);
|
||||
}
|
||||
|
||||
private async _executeMatchOrdersAsync(
|
||||
@ -203,7 +239,7 @@ function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): Mat
|
||||
function simulateMatchOrders(
|
||||
orders: MatchedOrders,
|
||||
takerAddress: string,
|
||||
tokenBalances: TokenBalancesByOwner,
|
||||
tokenBalances: TokenBalances,
|
||||
transferAmounts: MatchTransferAmounts,
|
||||
): MatchResults {
|
||||
// prettier-ignore
|
||||
@ -336,8 +372,47 @@ function transferAsset(
|
||||
const tokenId = erc721AssetData.tokenId;
|
||||
const fromTokens = matchResults.balances.erc721[fromAddress][assetAddress];
|
||||
const toTokens = matchResults.balances.erc721[toAddress][assetAddress];
|
||||
_.remove(fromTokens, tokenId);
|
||||
toTokens.push(tokenId);
|
||||
if (amount.gte(1)) {
|
||||
const tokenIndex = _.findIndex(fromTokens, t => t.eq(tokenId));
|
||||
if (tokenIndex !== -1) {
|
||||
fromTokens.splice(tokenIndex, 1);
|
||||
toTokens.push(tokenId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AssetProxyId.ERC1155: {
|
||||
const erc1155AssetData = assetDataUtils.decodeERC1155AssetData(assetData);
|
||||
const assetAddress = erc1155AssetData.tokenAddress;
|
||||
const fromBalances = matchResults.balances.erc1155[fromAddress][assetAddress];
|
||||
const toBalances = matchResults.balances.erc1155[toAddress][assetAddress];
|
||||
for (const i of _.times(erc1155AssetData.tokenIds.length)) {
|
||||
const tokenId = erc1155AssetData.tokenIds[i];
|
||||
const tokenValue = erc1155AssetData.tokenValues[i];
|
||||
const tokenAmount = amount.times(tokenValue);
|
||||
if (tokenAmount.gt(0)) {
|
||||
const tokenIndex = _.findIndex(fromBalances.nonFungible, t => t.eq(tokenId));
|
||||
if (tokenIndex !== -1) {
|
||||
// Transfer a non-fungible.
|
||||
fromBalances.nonFungible.splice(tokenIndex, 1);
|
||||
toBalances.nonFungible.push(tokenId);
|
||||
} else {
|
||||
// Transfer a fungible.
|
||||
const _tokenId = tokenId.toString(10);
|
||||
fromBalances.fungible[_tokenId] = fromBalances.fungible[_tokenId].minus(tokenAmount);
|
||||
toBalances.fungible[_tokenId] = toBalances.fungible[_tokenId].plus(tokenAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AssetProxyId.MultiAsset: {
|
||||
const multiAssetData = assetDataUtils.decodeMultiAssetData(assetData);
|
||||
for (const i of _.times(multiAssetData.amounts.length)) {
|
||||
const nestedAmount = amount.times(multiAssetData.amounts[i]);
|
||||
const nestedAssetData = multiAssetData.nestedAssetData[i];
|
||||
transferAsset(fromAddress, toAddress, nestedAmount, nestedAssetData, matchResults);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -355,7 +430,7 @@ function transferAsset(
|
||||
async function assertMatchResultsAsync(
|
||||
matchResults: MatchResults,
|
||||
transactionReceipt: TransactionReceiptWithDecodedLogs,
|
||||
actualTokenBalances: TokenBalancesByOwner,
|
||||
actualTokenBalances: TokenBalances,
|
||||
exchangeWrapper: ExchangeWrapper,
|
||||
): Promise<void> {
|
||||
// Check the fill events.
|
||||
@ -462,21 +537,8 @@ function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs
|
||||
* @param expectedBalances Expected balances.
|
||||
* @param actualBalances Actual balances.
|
||||
*/
|
||||
function assertBalances(expectedBalances: TokenBalancesByOwner, actualBalances: TokenBalancesByOwner): void {
|
||||
// ERC20 Balances
|
||||
expect(actualBalances.erc20, 'ERC20 balances').to.deep.equal(expectedBalances.erc20);
|
||||
// ERC721 Token Ids
|
||||
const sortedExpectedERC721Balances = _.mapValues(expectedBalances.erc721, tokenIdsByOwner => {
|
||||
_.mapValues(tokenIdsByOwner, tokenIds => {
|
||||
_.sortBy(tokenIds);
|
||||
});
|
||||
});
|
||||
const sortedActualERC721Balances = _.mapValues(actualBalances.erc721, tokenIdsByOwner => {
|
||||
_.mapValues(tokenIdsByOwner, tokenIds => {
|
||||
_.sortBy(tokenIds);
|
||||
});
|
||||
});
|
||||
expect(sortedExpectedERC721Balances, 'ERC721 balances').to.deep.equal(sortedActualERC721Balances);
|
||||
function assertBalances(expectedBalances: TokenBalances, actualBalances: TokenBalances): void {
|
||||
expect(encodeTokenBalances(expectedBalances)).to.deep.equal(encodeTokenBalances(actualBalances));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -533,17 +595,57 @@ async function assertPostExchangeStateAsync(
|
||||
* Retrive the current token balances of all known addresses.
|
||||
* @param erc20Wrapper The ERC20Wrapper instance.
|
||||
* @param erc721Wrapper The ERC721Wrapper instance.
|
||||
* @return A promise that resolves to a `TokenBalancesByOwner`.
|
||||
* @param erc1155Wrapper The ERC1155ProxyWrapper instance.
|
||||
* @return A promise that resolves to a `TokenBalances`.
|
||||
*/
|
||||
export async function getTokenBalancesAsync(
|
||||
erc20Wrapper: ERC20Wrapper,
|
||||
erc721Wrapper: ERC721Wrapper,
|
||||
): Promise<TokenBalancesByOwner> {
|
||||
const [erc20, erc721] = await Promise.all([erc20Wrapper.getBalancesAsync(), erc721Wrapper.getBalancesAsync()]);
|
||||
erc1155ProxyWrapper: ERC1155ProxyWrapper,
|
||||
): Promise<TokenBalances> {
|
||||
const [erc20, erc721, erc1155] = await Promise.all([
|
||||
erc20Wrapper.getBalancesAsync(),
|
||||
erc721Wrapper.getBalancesAsync(),
|
||||
erc1155ProxyWrapper.getBalancesAsync(),
|
||||
]);
|
||||
return {
|
||||
erc20,
|
||||
erc721,
|
||||
erc1155: transformERC1155Holdings(erc1155),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Restructures `ERC1155HoldingsByOwner` to be compatible with `TokenBalances.erc1155`.
|
||||
* @param erc1155HoldingsByOwner Holdings returned by `ERC1155ProxyWrapper.getBalancesAsync()`.
|
||||
*/
|
||||
function transformERC1155Holdings(erc1155HoldingsByOwner: ERC1155HoldingsByOwner): ERC1155Holdings {
|
||||
const result = {};
|
||||
for (const owner of _.keys(erc1155HoldingsByOwner.fungible)) {
|
||||
for (const contract of _.keys(erc1155HoldingsByOwner.fungible[owner])) {
|
||||
_.set(result as any, [owner, contract, 'fungible'], erc1155HoldingsByOwner.fungible[owner][contract]);
|
||||
}
|
||||
}
|
||||
for (const owner of _.keys(erc1155HoldingsByOwner.nonFungible)) {
|
||||
for (const contract of _.keys(erc1155HoldingsByOwner.nonFungible[owner])) {
|
||||
const tokenIds = _.flatten(_.values(erc1155HoldingsByOwner.nonFungible[owner][contract]));
|
||||
_.set(result as any, [owner, contract, 'nonFungible'], _.uniqBy(tokenIds, v => v.toString(10)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function encodeTokenBalances(obj: any): any {
|
||||
if (!_.isPlainObject(obj)) {
|
||||
if (BigNumber.isBigNumber(obj)) {
|
||||
return obj.toString(10);
|
||||
}
|
||||
if (_.isArray(obj)) {
|
||||
return _.sortBy(obj, v => encodeTokenBalances(v));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
const keys = _.keys(obj).sort();
|
||||
return _.zip(keys, keys.map(k => encodeTokenBalances(obj[k])));
|
||||
}
|
||||
// tslint:disable-line:max-file-line-count
|
||||
|
Loading…
x
Reference in New Issue
Block a user