aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/contracts/src/utils/core_combinatorial_utils.ts343
-rw-r--r--packages/contracts/src/utils/types.ts15
-rw-r--r--packages/contracts/test/exchange/core.ts43
-rw-r--r--packages/contracts/test/exchange/fill_order.ts57
4 files changed, 380 insertions, 78 deletions
diff --git a/packages/contracts/src/utils/core_combinatorial_utils.ts b/packages/contracts/src/utils/core_combinatorial_utils.ts
index 776d7b2fe..1735b3191 100644
--- a/packages/contracts/src/utils/core_combinatorial_utils.ts
+++ b/packages/contracts/src/utils/core_combinatorial_utils.ts
@@ -38,13 +38,13 @@ import {
OrderScenario,
TakerAssetFillAmountScenario,
TakerScenario,
+ TokenAmountScenario,
+ TraderStateScenario,
} from '../utils/types';
chaiSetup.configure();
const expect = chai.expect;
-const ERC721_PROXY_ID = 2;
-
/**
* Instantiates a new instance of CoreCombinatorialUtils. Since this method has some
* required async setup, a factory method is required.
@@ -192,6 +192,18 @@ export class CoreCombinatorialUtils {
takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario,
},
takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario,
+ makerStateScenario: {
+ traderAssetBalance: TokenAmountScenario.Higher,
+ traderAssetAllowance: TokenAmountScenario.Higher,
+ zrxFeeBalance: TokenAmountScenario.Higher,
+ zrxFeeAllowance: TokenAmountScenario.Higher,
+ },
+ takerStateScenario: {
+ traderAssetBalance: TokenAmountScenario.Higher,
+ traderAssetAllowance: TokenAmountScenario.Higher,
+ zrxFeeBalance: TokenAmountScenario.Higher,
+ zrxFeeAllowance: TokenAmountScenario.Higher,
+ },
};
return fillScenario;
});
@@ -248,24 +260,305 @@ export class CoreCombinatorialUtils {
signature: `0x${signature.toString('hex')}`,
};
- // 3. Permutate the maker and taker balance/allowance scenarios
- // TODO(fabio)
-
- // 4. Figure out fill amount
const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper);
const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(
this.exchangeWrapper,
this.zrxAssetData,
);
- const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher);
+ // 3. Figure out fill amount
+ const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync(
+ signedOrder,
+ fillScenario.takerAssetFillAmountScenario,
+ balanceAndProxyAllowanceFetcher,
+ orderFilledCancelledFetcher,
+ );
+
+ // 4. Permutate the maker and taker balance/allowance scenarios
+ await this._modifyTraderStateAsync(
+ fillScenario.makerStateScenario,
+ fillScenario.takerStateScenario,
+ signedOrder,
+ takerAssetFillAmount,
+ balanceAndProxyAllowanceFetcher,
+ );
+
+ // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts exp?
+ const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher);
+ const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
+ const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
+
+ let isFillFailureExpected = false;
+ try {
+ await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTransferSimulator,
+ provider,
+ signedOrder,
+ takerAssetFillAmount,
+ this.takerAddress,
+ this.zrxAssetData,
+ );
+ } catch (err) {
+ isFillFailureExpected = true;
+ }
+
+ await this._fillOrderAndAssertOutcomeAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ lazyStore,
+ isFillFailureExpected,
+ provider,
+ );
+ }
+ private async _modifyTraderStateAsync(
+ makerStateScenario: TraderStateScenario,
+ takerStateScenario: TraderStateScenario,
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher,
+ ): Promise<void> {
+ const makerAssetFillAmount = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+ switch (makerStateScenario.traderAssetBalance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ if (makerAssetFillAmount.eq(0)) {
+ throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`);
+ }
+ const tooLowBalance = makerAssetFillAmount.minus(1);
+ await this.assetWrapper.setBalanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ tooLowBalance,
+ );
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactBalance = makerAssetFillAmount;
+ await this.assetWrapper.setBalanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ exactBalance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.traderAssetBalance',
+ makerStateScenario.traderAssetBalance,
+ );
+ }
+
+ const makerFee = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ switch (makerStateScenario.zrxFeeBalance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ if (makerFee.eq(0)) {
+ throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`);
+ }
+ const tooLowBalance = makerFee.minus(1);
+ await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance);
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactBalance = makerFee;
+ await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance);
+ }
+
+ switch (makerStateScenario.traderAssetAllowance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ const tooLowAllowance = makerAssetFillAmount.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactAllowance = makerAssetFillAmount;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ exactAllowance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.traderAssetAllowance',
+ makerStateScenario.traderAssetAllowance,
+ );
+ }
+
+ switch (makerStateScenario.zrxFeeAllowance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ const tooLowAllowance = makerFee.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactAllowance = makerFee;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ exactAllowance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.zrxFeeAllowance',
+ makerStateScenario.zrxFeeAllowance,
+ );
+ }
+
+ switch (takerStateScenario.traderAssetBalance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ if (takerAssetFillAmount.eq(0)) {
+ throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`);
+ }
+ const tooLowBalance = takerAssetFillAmount.minus(1);
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance);
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactBalance = takerAssetFillAmount;
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.traderAssetBalance',
+ takerStateScenario.traderAssetBalance,
+ );
+ }
+
+ const takerFee = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ switch (takerStateScenario.zrxFeeBalance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ if (takerFee.eq(0)) {
+ throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`);
+ }
+ const tooLowBalance = takerFee.minus(1);
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance);
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactBalance = takerFee;
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance);
+ }
+
+ switch (takerStateScenario.traderAssetAllowance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ const tooLowAllowance = takerAssetFillAmount.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactAllowance = takerAssetFillAmount;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ exactAllowance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.traderAssetAllowance',
+ takerStateScenario.traderAssetAllowance,
+ );
+ }
+
+ switch (takerStateScenario.zrxFeeAllowance) {
+ case TokenAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case TokenAmountScenario.TooLow:
+ const tooLowAllowance = takerFee.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case TokenAmountScenario.Exact:
+ const exactAllowance = takerFee;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ exactAllowance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.zrxFeeAllowance',
+ takerStateScenario.zrxFeeAllowance,
+ );
+ }
+ }
+ private async _getTakerAssetFillAmountAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario,
+ balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher,
+ orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher,
+ ): Promise<BigNumber> {
+ const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher);
const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync(
signedOrder,
this.takerAddress,
);
let takerAssetFillAmount;
- const takerAssetFillAmountScenario = fillScenario.takerAssetFillAmountScenario;
switch (takerAssetFillAmountScenario) {
case TakerAssetFillAmountScenario.Zero:
takerAssetFillAmount = new BigNumber(0);
@@ -283,7 +576,7 @@ export class CoreCombinatorialUtils {
const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData);
const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.makerAssetData);
const isEitherAssetERC721 =
- takerAssetProxyId === ERC721_PROXY_ID || makerAssetProxyId === ERC721_PROXY_ID;
+ takerAssetProxyId === constants.ERC721_PROXY_ID || makerAssetProxyId === constants.ERC721_PROXY_ID;
if (isEitherAssetERC721) {
throw new Error(
'Cannot test `TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.',
@@ -296,32 +589,7 @@ export class CoreCombinatorialUtils {
throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario);
}
- // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts exp?
- const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher);
- const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
- const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
-
- let isFillFailureExpected = false;
- try {
- await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
- exchangeTransferSimulator,
- provider,
- signedOrder,
- takerAssetFillAmount,
- this.takerAddress,
- this.zrxAssetData,
- );
- } catch (err) {
- isFillFailureExpected = true;
- }
-
- await this._fillOrderAndAssertOutcomeAsync(
- signedOrder,
- takerAssetFillAmount,
- lazyStore,
- isFillFailureExpected,
- provider,
- );
+ return takerAssetFillAmount;
}
private async _fillOrderAndAssertOutcomeAsync(
signedOrder: SignedOrder,
@@ -369,6 +637,11 @@ export class CoreCombinatorialUtils {
signedOrder.makerAssetAmount,
);
+ const beforeMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
+ makerAddress,
+ makerAssetData,
+ );
+
// - Let's fill the order!
const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, {
takerAssetFillAmount,
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index f0e2dde02..d71c14495 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -201,7 +201,22 @@ export interface OrderScenario {
takerAssetDataScenario: AssetDataScenario;
}
+export enum TokenAmountScenario {
+ Exact = 'EXACT',
+ TooLow = 'TOO_LOW',
+ Higher = 'HIGHER',
+}
+
+export interface TraderStateScenario {
+ traderAssetBalance: TokenAmountScenario;
+ traderAssetAllowance: TokenAmountScenario;
+ zrxFeeBalance: TokenAmountScenario;
+ zrxFeeAllowance: TokenAmountScenario;
+}
+
export interface FillScenario {
orderScenario: OrderScenario;
takerAssetFillAmountScenario: TakerAssetFillAmountScenario;
+ makerStateScenario: TraderStateScenario;
+ takerStateScenario: TraderStateScenario;
}
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 193754e70..453f05df2 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -148,49 +148,6 @@ describe('Exchange core', () => {
);
});
- it('should throw if maker erc20Balances are too low to fill order', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
- });
-
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if taker erc20Balances are too low to fill order', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
- });
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if maker allowances are too low to fill order', async () => {
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
- from: makerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
- it('should throw if taker allowances are too low to fill order', async () => {
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
- from: takerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- return expectRevertOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- );
- });
-
it('should throw if no value is filled', async () => {
signedOrder = orderFactory.newSignedOrder();
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
diff --git a/packages/contracts/test/exchange/fill_order.ts b/packages/contracts/test/exchange/fill_order.ts
index 2498ee27e..5e8dd6ffc 100644
--- a/packages/contracts/test/exchange/fill_order.ts
+++ b/packages/contracts/test/exchange/fill_order.ts
@@ -13,6 +13,7 @@ import {
OrderScenario,
TakerAssetFillAmountScenario,
TakerScenario,
+ TokenAmountScenario,
} from '../../src/utils/types';
chaiSetup.configure();
@@ -31,6 +32,18 @@ const defaultFillScenario = {
takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
},
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ traderAssetBalance: TokenAmountScenario.Higher,
+ traderAssetAllowance: TokenAmountScenario.Higher,
+ zrxFeeBalance: TokenAmountScenario.Higher,
+ zrxFeeAllowance: TokenAmountScenario.Higher,
+ },
+ takerStateScenario: {
+ traderAssetBalance: TokenAmountScenario.Higher,
+ traderAssetAllowance: TokenAmountScenario.Higher,
+ zrxFeeBalance: TokenAmountScenario.Higher,
+ zrxFeeAllowance: TokenAmountScenario.Higher,
+ },
};
describe('FillOrder Tests', () => {
@@ -144,6 +157,50 @@ describe('FillOrder Tests', () => {
};
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
});
+
+ it('should throw if maker erc20Balances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetBalance: TokenAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if taker erc20Balances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetBalance: TokenAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if maker allowances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: TokenAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if taker allowances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: TokenAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
});
describe('Testing Exchange of ERC721 Tokens', () => {