aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-06-13 22:09:04 +0800
committerFabio Berger <me@fabioberger.com>2018-06-13 22:09:04 +0800
commit61243b418e4d962cd8d8a1d7a49f04510b3c1c7f (patch)
tree76dc6976632f74dbf3434ce5780aec9ad33bde3f
parentfe75660e88ed0c37c4f3d461a644bd9305bf6183 (diff)
downloaddexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.tar
dexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.tar.gz
dexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.tar.bz2
dexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.tar.lz
dexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.tar.xz
dexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.tar.zst
dexon-sol-tools-61243b418e4d962cd8d8a1d7a49f04510b3c1c7f.zip
Implement initial set of orderFill combinatorial tests
-rw-r--r--packages/contracts/src/utils/core_combinatorial_utils.ts445
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts4
-rw-r--r--packages/contracts/src/utils/new_order_factory.ts130
-rw-r--r--packages/contracts/src/utils/order_info_utils.ts44
-rw-r--r--packages/contracts/src/utils/order_utils.ts7
-rw-r--r--packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts19
-rw-r--r--packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts20
-rw-r--r--packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts32
-rw-r--r--packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts24
-rw-r--r--packages/contracts/src/utils/types.ts15
-rw-r--r--packages/contracts/test/combinatorial_tests.ts262
-rw-r--r--packages/order-utils/src/exchange_transfer_simulator.ts10
-rw-r--r--packages/order-utils/src/order_state_utils.ts262
13 files changed, 784 insertions, 490 deletions
diff --git a/packages/contracts/src/utils/core_combinatorial_utils.ts b/packages/contracts/src/utils/core_combinatorial_utils.ts
new file mode 100644
index 000000000..6fbd8304e
--- /dev/null
+++ b/packages/contracts/src/utils/core_combinatorial_utils.ts
@@ -0,0 +1,445 @@
+import {
+ assetProxyUtils,
+ BalanceAndProxyAllowanceLazyStore,
+ ExchangeTransferSimulator,
+ orderHashUtils,
+ OrderStateUtils,
+ OrderValidationUtils,
+} from '@0xproject/order-utils';
+import { AssetProxyId, Order, SignatureType, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as chai from 'chai';
+import { BlockParamLiteral, LogWithDecodedArgs, Provider, TxData } from 'ethereum-types';
+// import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+import 'make-promises-safe';
+
+import { ExchangeContract, FillContractEventArgs } from '../generated_contract_wrappers/exchange';
+import { artifacts } from '../utils/artifacts';
+import { expectRevertOrAlwaysFailingTransactionAsync } from '../utils/assertions';
+import { AssetWrapper } from '../utils/asset_wrapper';
+import { chaiSetup } from '../utils/chai_setup';
+import { constants } from '../utils/constants';
+import { ERC20Wrapper } from '../utils/erc20_wrapper';
+import { ERC721Wrapper } from '../utils/erc721_wrapper';
+import { ExchangeWrapper } from '../utils/exchange_wrapper';
+import { NewOrderFactory } from '../utils/new_order_factory';
+import { orderUtils } from '../utils/order_utils';
+import { signingUtils } from '../utils/signing_utils';
+import { SimpleAssetBalanceAndProxyAllowanceFetcher } from '../utils/simple_asset_balance_and_proxy_allowance_fetcher';
+import { SimpleOrderFilledCancelledFetcher } from '../utils/simple_order_filled_cancelled_fetcher';
+import {
+ AssetDataScenario,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ OrderAmountScenario,
+ OrderScenario,
+} 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.
+ * @param web3Wrapper Web3Wrapper instance
+ * @param txDefaults Default Ethereum tx options
+ * @return CoreCombinatorialUtils instance
+ */
+export async function coreCombinatorialUtilsFactoryAsync(
+ web3Wrapper: Web3Wrapper,
+ txDefaults: Partial<TxData>,
+): Promise<CoreCombinatorialUtils> {
+ const userAddresses = await web3Wrapper.getAvailableAddressesAsync();
+ const [ownerAddress, makerAddress, takerAddress] = userAddresses;
+ const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
+
+ const provider = web3Wrapper.getProvider();
+ const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress);
+ const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress);
+
+ const erc20EighteenDecimalTokenCount = 3;
+ const eighteenDecimals = new BigNumber(18);
+ const [
+ erc20EighteenDecimalTokenA,
+ erc20EighteenDecimalTokenB,
+ zrxToken,
+ ] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals);
+ const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
+
+ const erc20FiveDecimalTokenCount = 2;
+ const fiveDecimals = new BigNumber(18);
+ const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync(
+ erc20FiveDecimalTokenCount,
+ fiveDecimals,
+ );
+ const erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+
+ const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ const erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+
+ const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]);
+
+ const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync(
+ artifacts.Exchange,
+ provider,
+ txDefaults,
+ zrxAssetData,
+ );
+ const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, ownerAddress);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, ownerAddress);
+
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, {
+ from: ownerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, {
+ from: ownerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+
+ const orderFactory = new NewOrderFactory(
+ userAddresses,
+ zrxToken.address,
+ [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address],
+ [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address],
+ erc721Token,
+ erc721Balances,
+ exchangeContract.address,
+ );
+
+ const coreCombinatorialUtils = new CoreCombinatorialUtils(
+ orderFactory,
+ ownerAddress,
+ makerAddress,
+ makerPrivateKey,
+ takerAddress,
+ zrxAssetData,
+ exchangeWrapper,
+ assetWrapper,
+ );
+ return coreCombinatorialUtils;
+}
+
+export class CoreCombinatorialUtils {
+ public orderFactory: NewOrderFactory;
+ public ownerAddress: string;
+ public makerAddress: string;
+ public makerPrivateKey: Buffer;
+ public takerAddress: string;
+ public zrxAssetData: string;
+ public exchangeWrapper: ExchangeWrapper;
+ public assetWrapper: AssetWrapper;
+ public static generateOrderCombinations(): OrderScenario[] {
+ const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress];
+ const makerAssetAmountScenario = [OrderAmountScenario.NonZero];
+ const takerAssetAmountScenario = [OrderAmountScenario.NonZero];
+ const makerFeeScenario = [OrderAmountScenario.NonZero];
+ const takerFeeScenario = [OrderAmountScenario.NonZero];
+ const expirationTimeSecondsScenario = [ExpirationTimeSecondsScenario.InFuture];
+ const makerAssetDataScenario = [
+ AssetDataScenario.ERC20FiveDecimals,
+ AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ AssetDataScenario.ERC721,
+ AssetDataScenario.ZRXFeeToken,
+ ];
+ const takerAssetDataScenario = [
+ AssetDataScenario.ERC20FiveDecimals,
+ AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ AssetDataScenario.ERC721,
+ AssetDataScenario.ZRXFeeToken,
+ ];
+ const orderScenarioArrays = CoreCombinatorialUtils._allPossibleCases([
+ feeRecipientScenarios,
+ makerAssetAmountScenario,
+ takerAssetAmountScenario,
+ makerFeeScenario,
+ takerFeeScenario,
+ expirationTimeSecondsScenario,
+ makerAssetDataScenario,
+ takerAssetDataScenario,
+ ]);
+
+ const orderScenarios = _.map(orderScenarioArrays, orderScenarioArray => {
+ const orderScenario: OrderScenario = {
+ feeRecipientScenario: orderScenarioArray[0] as FeeRecipientAddressScenario,
+ makerAssetAmountScenario: orderScenarioArray[1] as OrderAmountScenario,
+ takerAssetAmountScenario: orderScenarioArray[2] as OrderAmountScenario,
+ makerFeeScenario: orderScenarioArray[3] as OrderAmountScenario,
+ takerFeeScenario: orderScenarioArray[4] as OrderAmountScenario,
+ expirationTimeSecondsScenario: orderScenarioArray[5] as ExpirationTimeSecondsScenario,
+ makerAssetDataScenario: orderScenarioArray[6] as AssetDataScenario,
+ takerAssetDataScenario: orderScenarioArray[7] as AssetDataScenario,
+ };
+ return orderScenario;
+ });
+
+ return orderScenarios;
+ }
+ private static _allPossibleCases(arrays: string[][]): string[][] {
+ if (arrays.length === 1) {
+ const remainingVals = _.map(arrays[0], val => {
+ return [val];
+ });
+ return remainingVals;
+ } else {
+ const result = [];
+ const allCasesOfRest = CoreCombinatorialUtils._allPossibleCases(arrays.slice(1)); // recur with the rest of array
+ // tslint:disable:prefer-for-of
+ for (let i = 0; i < allCasesOfRest.length; i++) {
+ for (let j = 0; j < arrays[0].length; j++) {
+ result.push([arrays[0][j], ...allCasesOfRest[i]]);
+ }
+ }
+ // tslint:enable:prefer-for-of
+ return result;
+ }
+ }
+ constructor(
+ orderFactory: NewOrderFactory,
+ ownerAddress: string,
+ makerAddress: string,
+ makerPrivateKey: Buffer,
+ takerAddress: string,
+ zrxAssetData: string,
+ exchangeWrapper: ExchangeWrapper,
+ assetWrapper: AssetWrapper,
+ ) {
+ this.orderFactory = orderFactory;
+ this.ownerAddress = ownerAddress;
+ this.makerAddress = makerAddress;
+ this.makerPrivateKey = makerPrivateKey;
+ this.takerAddress = takerAddress;
+ this.zrxAssetData = zrxAssetData;
+ this.exchangeWrapper = exchangeWrapper;
+ this.assetWrapper = assetWrapper;
+ }
+ public async testFillOrderScenarioAsync(order: Order, provider: Provider): Promise<void> {
+ // 2. Sign order
+ const orderHashBuff = orderHashUtils.getOrderHashBuff(order);
+ const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign);
+ const signedOrder = {
+ ...order,
+ signature: `0x${signature.toString('hex')}`,
+ };
+
+ // 3. Permutate the maker and taker balance/allowance scenarios
+
+ // 4. Figure out fill amount OR error
+ const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper);
+ const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(
+ this.exchangeWrapper,
+ this.zrxAssetData,
+ );
+ const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher);
+
+ const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync(
+ signedOrder,
+ this.takerAddress,
+ );
+
+ // If order is fillable, decide how much to fill
+ // TODO: Make this configurable
+ const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData);
+ const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.makerAssetData);
+ const isEitherAssetERC721 = takerAssetProxyId === ERC721_PROXY_ID || makerAssetProxyId === ERC721_PROXY_ID;
+ const takerAssetFillAmount = isEitherAssetERC721
+ ? fillableTakerAssetAmount
+ : fillableTakerAssetAmount.div(2).floor();
+
+ // 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 _fillOrderAndAssertOutcomeAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ lazyStore: BalanceAndProxyAllowanceLazyStore,
+ isFillFailureExpected: boolean,
+ provider: Provider,
+ ): Promise<void> {
+ if (isFillFailureExpected) {
+ return expectRevertOrAlwaysFailingTransactionAsync(
+ this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }),
+ );
+ }
+
+ const makerAddress = signedOrder.makerAddress;
+ const makerAssetData = signedOrder.makerAssetData;
+ const takerAssetData = signedOrder.takerAssetData;
+ const feeRecipient = signedOrder.feeRecipientAddress;
+
+ const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress);
+ const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress);
+ const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress);
+ const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress);
+ const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress);
+ const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress);
+ const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress);
+ const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress);
+ const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress);
+ const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(
+ this.zrxAssetData,
+ this.takerAddress,
+ );
+ const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient);
+
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const initialFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
+ const expFilledTakerAmount = initialFilledTakerAmount.add(takerAssetFillAmount);
+
+ const expFilledMakerAmount = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+
+ // - Let's fill the order!
+ const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, {
+ takerAssetFillAmount,
+ });
+
+ const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
+ expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount');
+
+ expect(txReceipt.logs.length).to.be.equal(1, 'logs length');
+ // tslint:disable-next-line:no-unnecessary-type-assertion
+ const log = txReceipt.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
+ expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress');
+ expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress');
+ expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress');
+ expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal(
+ expFilledMakerAmount,
+ 'log.args.makerAssetFilledAmount',
+ );
+ expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
+ takerAssetFillAmount,
+ 'log.args.takerAssetFilledAmount',
+ );
+ const expMakerFeePaid = orderUtils.getPartialAmount(
+ expFilledTakerAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid');
+ const expTakerFeePaid = orderUtils.getPartialAmount(
+ expFilledTakerAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid');
+ expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash');
+ expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData');
+ expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData');
+
+ const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData);
+ expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal(
+ expMakerAssetBalanceOfMaker,
+ 'makerAssetBalanceOfMaker',
+ );
+
+ const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
+ makerAddress,
+ makerAssetData,
+ );
+ expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal(
+ expMakerAssetAllowanceOfMaker,
+ 'makerAssetAllowanceOfMaker',
+ );
+
+ const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData);
+ expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal(
+ expTakerAssetBalanceOfMaker,
+ 'takerAssetBalanceOfMaker',
+ );
+
+ const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData);
+ expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker');
+
+ const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
+ makerAddress,
+ this.zrxAssetData,
+ );
+ expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal(
+ expZRXAssetAllowanceOfMaker,
+ 'ZRXAssetAllowanceOfMaker',
+ );
+
+ const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData);
+ expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal(
+ expTakerAssetBalanceOfTaker,
+ 'TakerAssetBalanceOfTaker',
+ );
+
+ const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync(
+ this.takerAddress,
+ takerAssetData,
+ );
+
+ expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal(
+ expTakerAssetAllowanceOfTaker,
+ 'TakerAssetAllowanceOfTaker',
+ );
+
+ const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData);
+ expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal(
+ expMakerAssetBalanceOfTaker,
+ 'MakerAssetBalanceOfTaker',
+ );
+
+ const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData);
+ expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker');
+
+ const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync(
+ this.takerAddress,
+ this.zrxAssetData,
+ );
+ expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal(
+ expZRXAssetAllowanceOfTaker,
+ 'ZRXAssetAllowanceOfTaker',
+ );
+
+ const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync(
+ feeRecipient,
+ this.zrxAssetData,
+ );
+ expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal(
+ expZRXAssetBalanceOfFeeRecipient,
+ 'ZRXAssetBalanceOfFeeRecipient',
+ );
+ }
+}
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
index 1ecabcf64..e74b91fe2 100644
--- a/packages/contracts/src/utils/exchange_wrapper.ts
+++ b/packages/contracts/src/utils/exchange_wrapper.ts
@@ -227,6 +227,10 @@ export class ExchangeWrapper {
const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
return filledAmount;
}
+ public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
+ const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
+ return isCancelled;
+ }
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
return orderInfo;
diff --git a/packages/contracts/src/utils/new_order_factory.ts b/packages/contracts/src/utils/new_order_factory.ts
index a4ded4230..715e38d6d 100644
--- a/packages/contracts/src/utils/new_order_factory.ts
+++ b/packages/contracts/src/utils/new_order_factory.ts
@@ -2,7 +2,7 @@ import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-util
import { Order } from '@0xproject/types';
import { BigNumber, errorUtils } from '@0xproject/utils';
-import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token';
+import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token';
import { constants } from './constants';
import {
@@ -11,20 +11,13 @@ import {
ExpirationTimeSecondsScenario,
FeeRecipientAddressScenario,
OrderAmountScenario,
+ OrderScenario,
} from './types';
const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10000000000000000000);
const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100000000000000000);
const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1000000);
const ONE_NFT_UNIT = new BigNumber(1);
-const TEN_MINUTES_MS = 1000 * 60 * 10;
-
-/*
- * TODO:
- * - Write function that given an order, fillAmount, retrieves orderRelevantState and maps it to expected test outcome.
- * - Write function that generates order permutations.
- * - Write functions for other steps that must be permutated
- */
export class NewOrderFactory {
private _userAddresses: string[];
@@ -51,16 +44,7 @@ export class NewOrderFactory {
this._erc721Balances = erc721Balances;
this._exchangeAddress = exchangeAddress;
}
- public generateOrder(
- feeRecipientScenario: FeeRecipientAddressScenario,
- makerAssetAmountScenario: OrderAmountScenario,
- takerAssetAmountScenario: OrderAmountScenario,
- makerFeeScenario: OrderAmountScenario,
- takerFeeScenario: OrderAmountScenario,
- expirationTimeSecondsScenario: ExpirationTimeSecondsScenario,
- makerAssetDataScenario: AssetDataScenario,
- takerAssetDataScenario: AssetDataScenario,
- ): Order {
+ public generateOrder(orderScenario: OrderScenario): Order {
const makerAddress = this._userAddresses[1];
const takerAddress = this._userAddresses[2];
const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address];
@@ -74,7 +58,7 @@ export class NewOrderFactory {
let makerAssetData;
let takerAssetData;
- switch (feeRecipientScenario) {
+ switch (orderScenario.feeRecipientScenario) {
case FeeRecipientAddressScenario.BurnAddress:
feeRecipientAddress = constants.NULL_ADDRESS;
break;
@@ -82,135 +66,102 @@ export class NewOrderFactory {
feeRecipientAddress = this._userAddresses[4];
break;
default:
- throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', feeRecipientScenario);
+ throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario);
}
- const invalidAssetProxyIdHex = '0A';
- switch (makerAssetDataScenario) {
+ switch (orderScenario.makerAssetDataScenario) {
case AssetDataScenario.ZRXFeeToken:
- makerAssetData = assetProxyUtils.encodeERC20ProxyData(this._zrxAddress);
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
break;
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
- makerAssetData = assetProxyUtils.encodeERC20ProxyData(
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(
this._nonZrxERC20EighteenDecimalTokenAddresses[0],
);
break;
case AssetDataScenario.ERC20FiveDecimals:
- makerAssetData = assetProxyUtils.encodeERC20ProxyData(this._erc20FiveDecimalTokenAddresses[0]);
- break;
- case AssetDataScenario.ERC20InvalidAssetProxyId: {
- const validAssetData = assetProxyUtils.encodeERC20ProxyData(
- this._nonZrxERC20EighteenDecimalTokenAddresses[0],
- );
- makerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`;
- break;
- }
- case AssetDataScenario.ERC721ValidAssetProxyId:
- makerAssetData = assetProxyUtils.encodeERC721ProxyData(
- this._erc721Token.address,
- erc721MakerAssetIds[0],
- );
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]);
break;
- case AssetDataScenario.ERC721InvalidAssetProxyId: {
- const validAssetData = assetProxyUtils.encodeERC721ProxyData(
+ case AssetDataScenario.ERC721:
+ makerAssetData = assetProxyUtils.encodeERC721AssetData(
this._erc721Token.address,
erc721MakerAssetIds[0],
);
- makerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`;
break;
- }
default:
- throw errorUtils.spawnSwitchErr('AssetDataScenario', makerAssetDataScenario);
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
}
- switch (takerAssetDataScenario) {
+ switch (orderScenario.takerAssetDataScenario) {
case AssetDataScenario.ZRXFeeToken:
- takerAssetData = assetProxyUtils.encodeERC20ProxyData(this._zrxAddress);
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
break;
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
- takerAssetData = assetProxyUtils.encodeERC20ProxyData(
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(
this._nonZrxERC20EighteenDecimalTokenAddresses[1],
);
break;
case AssetDataScenario.ERC20FiveDecimals:
- takerAssetData = assetProxyUtils.encodeERC20ProxyData(this._erc20FiveDecimalTokenAddresses[1]);
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]);
break;
- case AssetDataScenario.ERC20InvalidAssetProxyId: {
- const validAssetData = assetProxyUtils.encodeERC20ProxyData(
- this._nonZrxERC20EighteenDecimalTokenAddresses[1],
- );
- takerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`;
- break;
- }
- case AssetDataScenario.ERC721ValidAssetProxyId:
- takerAssetData = assetProxyUtils.encodeERC721ProxyData(
- this._erc721Token.address,
- erc721TakerAssetIds[0],
- );
- break;
- case AssetDataScenario.ERC721InvalidAssetProxyId: {
- const validAssetData = assetProxyUtils.encodeERC721ProxyData(
+ case AssetDataScenario.ERC721:
+ takerAssetData = assetProxyUtils.encodeERC721AssetData(
this._erc721Token.address,
erc721TakerAssetIds[0],
);
- takerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`;
break;
- }
default:
- throw errorUtils.spawnSwitchErr('AssetDataScenario', takerAssetDataScenario);
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
}
- switch (makerAssetAmountScenario) {
+ switch (orderScenario.makerAssetAmountScenario) {
case OrderAmountScenario.NonZero:
- switch (makerAssetDataScenario) {
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
- case AssetDataScenario.ERC20InvalidAssetProxyId:
makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
break;
case AssetDataScenario.ERC20FiveDecimals:
makerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
break;
- case AssetDataScenario.ERC721ValidAssetProxyId:
- case AssetDataScenario.ERC721InvalidAssetProxyId:
+ case AssetDataScenario.ERC721:
makerAssetAmount = ONE_NFT_UNIT;
break;
default:
- throw errorUtils.spawnSwitchErr('AssetDataScenario', makerAssetDataScenario);
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
}
break;
case OrderAmountScenario.Zero:
makerAssetAmount = new BigNumber(0);
break;
default:
- throw errorUtils.spawnSwitchErr('OrderAmountScenario', makerAssetAmountScenario);
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.makerAssetAmountScenario);
}
- switch (takerAssetAmountScenario) {
+ switch (orderScenario.takerAssetAmountScenario) {
case OrderAmountScenario.NonZero:
- switch (takerAssetDataScenario) {
+ switch (orderScenario.takerAssetDataScenario) {
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
- case AssetDataScenario.ERC20InvalidAssetProxyId:
+ case AssetDataScenario.ZRXFeeToken:
takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
break;
case AssetDataScenario.ERC20FiveDecimals:
takerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
break;
- case AssetDataScenario.ERC721ValidAssetProxyId:
- case AssetDataScenario.ERC721InvalidAssetProxyId:
+ case AssetDataScenario.ERC721:
takerAssetAmount = ONE_NFT_UNIT;
break;
default:
- throw errorUtils.spawnSwitchErr('AssetDataScenario', takerAssetDataScenario);
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
}
break;
case OrderAmountScenario.Zero:
takerAssetAmount = new BigNumber(0);
break;
default:
- throw errorUtils.spawnSwitchErr('OrderAmountScenario', takerAssetAmountScenario);
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.takerAssetAmountScenario);
}
- switch (makerFeeScenario) {
+ switch (orderScenario.makerFeeScenario) {
case OrderAmountScenario.NonZero:
makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
break;
@@ -218,10 +169,10 @@ export class NewOrderFactory {
makerFee = new BigNumber(0);
break;
default:
- throw errorUtils.spawnSwitchErr('OrderAmountScenario', makerFeeScenario);
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.makerFeeScenario);
}
- switch (takerFeeScenario) {
+ switch (orderScenario.takerFeeScenario) {
case OrderAmountScenario.NonZero:
takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
break;
@@ -229,18 +180,21 @@ export class NewOrderFactory {
takerFee = new BigNumber(0);
break;
default:
- throw errorUtils.spawnSwitchErr('OrderAmountScenario', takerFeeScenario);
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.takerFeeScenario);
}
- switch (expirationTimeSecondsScenario) {
+ switch (orderScenario.expirationTimeSecondsScenario) {
case ExpirationTimeSecondsScenario.InFuture:
- expirationTimeSeconds = new BigNumber(Date.now() + TEN_MINUTES_MS);
+ expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite
break;
case ExpirationTimeSecondsScenario.InPast:
- expirationTimeSeconds = new BigNumber(Date.now() - TEN_MINUTES_MS);
+ expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970
break;
default:
- throw errorUtils.spawnSwitchErr('ExpirationTimeSecondsScenario', expirationTimeSecondsScenario);
+ throw errorUtils.spawnSwitchErr(
+ 'ExpirationTimeSecondsScenario',
+ orderScenario.expirationTimeSecondsScenario,
+ );
}
const order: Order = {
diff --git a/packages/contracts/src/utils/order_info_utils.ts b/packages/contracts/src/utils/order_info_utils.ts
deleted file mode 100644
index 9df627da3..000000000
--- a/packages/contracts/src/utils/order_info_utils.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { assetProxyUtils, OrderStateUtils } from '@0xproject/order-utils';
-import { SignedOrder } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-
-import { ExchangeContract } from '../contract_wrappers/generated/exchange';
-
-import { constants } from './constants';
-import { ERC20Wrapper } from './erc20_wrapper';
-import { SimpleERC20BalanceAndProxyAllowanceFetcher } from './simple_erc20_balance_and_allowance_fetcher';
-import { SimpleOrderFilledCancelledFetcher } from './simple_filled_cancelled_fetcher';
-
-export class OrderInfoUtils {
- private _orderStateUtils: OrderStateUtils;
- private _erc20Wrapper: ERC20Wrapper;
- constructor(exchangeContract: ExchangeContract, erc20Wrapper: ERC20Wrapper, zrxAddress: string) {
- this._erc20Wrapper = erc20Wrapper;
- const simpleOrderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(exchangeContract, zrxAddress);
- const simpleERC20BalanceAndProxyAllowanceFetcher = new SimpleERC20BalanceAndProxyAllowanceFetcher(erc20Wrapper);
- this._orderStateUtils = new OrderStateUtils(
- simpleERC20BalanceAndProxyAllowanceFetcher,
- simpleOrderFilledCancelledFetcher,
- );
- }
- public async getFillableTakerAssetAmountAsync(signedOrder: SignedOrder, takerAddress: string): Promise<BigNumber> {
- const orderRelevantState = await this._orderStateUtils.getOrderRelevantStateAsync(signedOrder);
- console.log('orderRelevantState', orderRelevantState);
- if (takerAddress === constants.NULL_ADDRESS) {
- return orderRelevantState.remainingFillableTakerAssetAmount;
- }
- const takerAssetData = assetProxyUtils.decodeERC20ProxyData(signedOrder.takerAssetData);
- const takerBalance = await this._erc20Wrapper.getBalanceAsync(takerAddress, takerAssetData.tokenAddress);
- const takerAllowance = await this._erc20Wrapper.getProxyAllowanceAsync(
- takerAddress,
- takerAssetData.tokenAddress,
- );
- // TODO: We also need to make sure taker has sufficient ZRX for fees...
- const fillableTakerAssetAmount = BigNumber.min([
- takerBalance,
- takerAllowance,
- orderRelevantState.remainingFillableTakerAssetAmount,
- ]);
- return fillableTakerAssetAmount;
- }
-}
diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts
index 2a8791e4c..40643fa75 100644
--- a/packages/contracts/src/utils/order_utils.ts
+++ b/packages/contracts/src/utils/order_utils.ts
@@ -4,6 +4,13 @@ import { BigNumber } from '@0xproject/utils';
import { CancelOrder, MatchOrder } from './types';
export const orderUtils = {
+ getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
+ const partialAmount = numerator
+ .mul(target)
+ .div(denominator)
+ .floor();
+ return partialAmount;
+ },
createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => {
const fill = {
order: orderUtils.getOrderWithoutExchangeAddress(signedOrder),
diff --git a/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts
new file mode 100644
index 000000000..a295a40c4
--- /dev/null
+++ b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts
@@ -0,0 +1,19 @@
+import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+
+import { AssetWrapper } from './asset_wrapper';
+
+export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
+ private _assetWrapper: AssetWrapper;
+ constructor(assetWrapper: AssetWrapper) {
+ this._assetWrapper = assetWrapper;
+ }
+ public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
+ const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData);
+ return balance;
+ }
+ public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
+ const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData);
+ return proxyAllowance;
+ }
+}
diff --git a/packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts b/packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts
deleted file mode 100644
index 6eed9227a..000000000
--- a/packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils';
-import { BigNumber } from '@0xproject/utils';
-
-import { ERC20Wrapper } from './erc20_wrapper';
-
-// TODO(fabio): Refactor this to also work for ERC721!
-export class SimpleERC20BalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
- private _erc20TokenWrapper: ERC20Wrapper;
- constructor(erc20TokenWrapper: ERC20Wrapper) {
- this._erc20TokenWrapper = erc20TokenWrapper;
- }
- public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
- const balance = await this._erc20TokenWrapper.getBalanceAsync(userAddress, tokenAddress);
- return balance;
- }
- public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
- const proxyAllowance = await this._erc20TokenWrapper.getProxyAllowanceAsync(userAddress, tokenAddress);
- return proxyAllowance;
- }
-}
diff --git a/packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts b/packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts
deleted file mode 100644
index 0a80637cc..000000000
--- a/packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils';
-import { BigNumber } from '@0xproject/utils';
-import { BlockParamLiteral } from 'ethereum-types';
-
-import {
- CancelContractEventArgs,
- ExchangeContract,
- FillContractEventArgs,
-} from '../contract_wrappers/generated/exchange';
-
-export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher {
- private _exchangeContract: ExchangeContract;
- private _zrxAddress: string;
- constructor(exchange: ExchangeContract, zrxAddress: string) {
- this._exchangeContract = exchange;
- this._zrxAddress = zrxAddress;
- }
- public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
- const filledTakerAmount = new BigNumber(await this._exchangeContract.filled.callAsync(orderHash));
- return filledTakerAmount;
- }
- public async isOrderCancelledAsync(orderHash: string): Promise<boolean> {
- const methodOpts = {
- defaultBlock: BlockParamLiteral.Latest,
- };
- const isCancelled = await this._exchangeContract.cancelled.callAsync(orderHash);
- return isCancelled;
- }
- public getZRXTokenAddress(): string {
- return this._zrxAddress;
- }
-}
diff --git a/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts
new file mode 100644
index 000000000..24afe36b7
--- /dev/null
+++ b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts
@@ -0,0 +1,24 @@
+import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+
+import { ExchangeWrapper } from './exchange_wrapper';
+
+export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher {
+ private _exchangeWrapper: ExchangeWrapper;
+ private _zrxAssetData: string;
+ constructor(exchange: ExchangeWrapper, zrxAssetData: string) {
+ this._exchangeWrapper = exchange;
+ this._zrxAssetData = zrxAssetData;
+ }
+ public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
+ const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash));
+ return filledTakerAmount;
+ }
+ public async isOrderCancelledAsync(orderHash: string): Promise<boolean> {
+ const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash);
+ return isCancelled;
+ }
+ public getZRXAssetData(): string {
+ return this._zrxAssetData;
+ }
+}
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index a6e1cd6d0..77e8d867e 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -169,10 +169,19 @@ export enum ExpirationTimeSecondsScenario {
}
export enum AssetDataScenario {
- ERC721ValidAssetProxyId = 'ERC721_VALID_ASSET_PROXY_ID',
- ERC721InvalidAssetProxyId = 'ERC721_INVALID_ASSET_PROXY_ID',
+ ERC721 = 'ERC721',
ZRXFeeToken = 'ZRX_FEE_TOKEN',
- ERC20InvalidAssetProxyId = 'ERC20_INVALID_ASSET_PROXY_ID',
ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS',
ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS',
}
+
+export interface OrderScenario {
+ feeRecipientScenario: FeeRecipientAddressScenario;
+ makerAssetAmountScenario: OrderAmountScenario;
+ takerAssetAmountScenario: OrderAmountScenario;
+ makerFeeScenario: OrderAmountScenario;
+ takerFeeScenario: OrderAmountScenario;
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario;
+ makerAssetDataScenario: AssetDataScenario;
+ takerAssetDataScenario: AssetDataScenario;
+}
diff --git a/packages/contracts/test/combinatorial_tests.ts b/packages/contracts/test/combinatorial_tests.ts
index 7a118f6ac..c7de8d7e3 100644
--- a/packages/contracts/test/combinatorial_tests.ts
+++ b/packages/contracts/test/combinatorial_tests.ts
@@ -1,266 +1,48 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
-import { assetProxyUtils, crypto, orderHashUtils, OrderStateUtils } from '@0xproject/order-utils';
-import { AssetProxyId, SignatureType, SignedOrder } from '@0xproject/types';
-import { BigNumber } from '@0xproject/utils';
-import { Web3Wrapper } from '@0xproject/web3-wrapper';
-import * as chai from 'chai';
-import { LogWithDecodedArgs } from 'ethereum-types';
-import ethUtil = require('ethereumjs-util');
-import 'make-promises-safe';
+import * as _ from 'lodash';
-import { DummyERC20TokenContract } from '../src/contract_wrappers/generated/dummy_e_r_c20_token';
-import { DummyERC721TokenContract } from '../src/contract_wrappers/generated/dummy_e_r_c721_token';
-import { ERC20ProxyContract } from '../src/contract_wrappers/generated/e_r_c20_proxy';
-import { ERC721ProxyContract } from '../src/contract_wrappers/generated/e_r_c721_proxy';
-import {
- CancelContractEventArgs,
- ExchangeContract,
- FillContractEventArgs,
-} from '../src/contract_wrappers/generated/exchange';
-import { artifacts } from '../src/utils/artifacts';
import { chaiSetup } from '../src/utils/chai_setup';
-import { constants } from '../src/utils/constants';
-import { ERC20Wrapper } from '../src/utils/erc20_wrapper';
-import { ERC721Wrapper } from '../src/utils/erc721_wrapper';
-import { ExchangeWrapper } from '../src/utils/exchange_wrapper';
-import { NewOrderFactory } from '../src/utils/new_order_factory';
-import { OrderInfoUtils } from '../src/utils/order_info_utils';
-import { orderUtils } from '../src/utils/order_utils';
-import { signingUtils } from '../src/utils/signing_utils';
-import {
- AssetDataScenario,
- ContractName,
- ERC20BalancesByOwner,
- ExpirationTimeSecondsScenario,
- FeeRecipientAddressScenario,
- OrderAmountScenario,
- OrderStatus,
-} from '../src/utils/types';
-
+import { CoreCombinatorialUtils, coreCombinatorialUtilsFactoryAsync } from '../src/utils/core_combinatorial_utils';
import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper';
+import { OrderScenario } from '../src/utils/types';
+
chaiSetup.configure();
-const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Combinatorial tests', () => {
- let newOrderFactory: NewOrderFactory;
- let usedAddresses: string[];
-
- let makerAddress: string;
- let owner: string;
- let takerAddress: string;
- let feeRecipientAddress: string;
-
- let erc20EighteenDecimalTokenA: DummyERC20TokenContract;
- let erc20EighteenDecimalTokenB: DummyERC20TokenContract;
- let erc20FiveDecimalTokenA: DummyERC20TokenContract;
- let erc20FiveDecimalTokenB: DummyERC20TokenContract;
- let zrxToken: DummyERC20TokenContract;
- let erc721Token: DummyERC721TokenContract;
- let exchange: ExchangeContract;
- let erc20Proxy: ERC20ProxyContract;
- let erc721Proxy: ERC721ProxyContract;
-
- let erc20Balances: ERC20BalancesByOwner;
- let exchangeWrapper: ExchangeWrapper;
- let erc20Wrapper: ERC20Wrapper;
- let erc721Wrapper: ERC721Wrapper;
-
- let erc721MakerAssetIds: BigNumber[];
- let erc721TakerAssetIds: BigNumber[];
-
- let defaultMakerAssetAddress: string;
- let defaultTakerAssetAddress: string;
+ let coreCombinatorialUtils: CoreCombinatorialUtils;
before(async () => {
await blockchainLifecycle.startAsync();
+ coreCombinatorialUtils = await coreCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
- before(async () => {
- const accounts = await web3Wrapper.getAvailableAddressesAsync();
- usedAddresses = [owner, makerAddress, takerAddress, feeRecipientAddress] = accounts;
-
- erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
- erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
-
- const erc20EighteenDecimalTokenCount = 3;
- const eighteenDecimals = new BigNumber(18);
- [erc20EighteenDecimalTokenA, erc20EighteenDecimalTokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
- erc20EighteenDecimalTokenCount,
- eighteenDecimals,
- );
-
- const erc20FiveDecimalTokenCount = 2;
- const fiveDecimals = new BigNumber(18);
- [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync(
- erc20FiveDecimalTokenCount,
- fiveDecimals,
- );
- erc20Proxy = await erc20Wrapper.deployProxyAsync();
- await erc20Wrapper.setBalancesAndAllowancesAsync();
-
- [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
- erc721Proxy = await erc721Wrapper.deployProxyAsync();
- await erc721Wrapper.setBalancesAndAllowancesAsync();
- const erc721Balances = await erc721Wrapper.getBalancesAsync();
- erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address];
- erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address];
-
- exchange = await ExchangeContract.deployFrom0xArtifactAsync(
- artifacts.Exchange,
- provider,
- txDefaults,
- assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
- );
- exchangeWrapper = new ExchangeWrapper(exchange, provider);
- await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
- await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, owner);
-
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
- from: owner,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
- from: owner,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
-
- defaultMakerAssetAddress = erc20EighteenDecimalTokenA.address;
- defaultTakerAssetAddress = erc20EighteenDecimalTokenB.address;
-
- newOrderFactory = new NewOrderFactory(
- usedAddresses,
- zrxToken.address,
- [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address],
- [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address],
- erc721Token,
- erc721Balances,
- exchange.address,
- );
- });
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
- describe.only('Fill order', () => {
- beforeEach(async () => {
- erc20Balances = await erc20Wrapper.getBalancesAsync();
+ const test = (orderScenarios: OrderScenario[]) => {
+ _.forEach(orderScenarios, orderScenario => {
+ const description = `Combinatorial OrderFill: ${orderScenario.feeRecipientScenario} ${
+ orderScenario.makerAssetAmountScenario
+ } ${orderScenario.takerAssetAmountScenario} ${orderScenario.makerFeeScenario} ${
+ orderScenario.takerFeeScenario
+ } ${orderScenario.expirationTimeSecondsScenario} ${orderScenario.makerAssetDataScenario} ${
+ orderScenario.takerAssetDataScenario
+ }`;
+ it(description, async () => {
+ const order = coreCombinatorialUtils.orderFactory.generateOrder(orderScenario);
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(order, provider);
+ });
});
- it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
- const order = newOrderFactory.generateOrder(
- FeeRecipientAddressScenario.EthUserAddress,
- OrderAmountScenario.NonZero,
- OrderAmountScenario.NonZero,
- OrderAmountScenario.Zero,
- OrderAmountScenario.Zero,
- ExpirationTimeSecondsScenario.InFuture,
- AssetDataScenario.ERC20NonZRXEighteenDecimals,
- AssetDataScenario.ERC20NonZRXEighteenDecimals,
- );
-
- // TODO: Permute signature types
+ };
- // TODO: Sign order (for now simply ECSign)
- const orderHashBuff = orderHashUtils.getOrderHashBuff(order);
- const privateKey = constants.TESTRPC_PRIVATE_KEYS[usedAddresses.indexOf(makerAddress)];
- const signature = signingUtils.signMessage(orderHashBuff, privateKey, SignatureType.EthSign);
- const signedOrder = {
- ...order,
- signature: `0x${signature.toString('hex')}`,
- };
- console.log('signedOrder', signedOrder);
+ const allOrderScenarios = CoreCombinatorialUtils.generateOrderCombinations();
- // TODO: Get orderRelevantState
- const orderInfoUtils = new OrderInfoUtils(exchange, erc20Wrapper, zrxToken.address);
- // 1. How much of this order can I fill?
- const fillableTakerAssetAmount = await orderInfoUtils.getFillableTakerAssetAmountAsync(
- signedOrder,
- takerAddress,
- );
- console.log('fillableTakerAssetAmount', fillableTakerAssetAmount);
-
- // TODO: Decide how much to fill (all, some)
- const takerFillAmount = fillableTakerAssetAmount.div(2); // some for now
-
- // 2. If I fill it by X, what are the resulting balances/allowances/filled amounts expected?
- // NOTE: we can't use orderStateUtils for this :( We need to do this ourselves.
-
- // This doesn't include taker balance/allowance checks...
- /*
- Inputs:
- - signedOrder
- - takerAddress
- Outputs:
- - Check fillable amount
- - maker token balance & allowance
- - maker ZRX balance & allowance
- - taker token balance & allowance
- - taker ZRX balance & allowance
- Test:
- - If fillable >= fillAmount:
- - check that filled by fillAmount
- - check makerBalance
- */
-
- // signedOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- // }); );
- //
- // const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- // orderHashUtils.getOrderHashHex(signedOrder),
- // );
- // expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
- //
- // const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
- // await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- //
- // const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
- // orderHashUtils.getOrderHashHex(signedOrder),
- // );
- // expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
- //
- // const newBalances = await erc20Wrapper.getBalancesAsync();
- //
- // const makerAssetFilledAmount = takerAssetFillAmount
- // .times(signedOrder.makerAssetAmount)
- // .dividedToIntegerBy(signedOrder.takerAssetAmount);
- // const makerFeePaid = signedOrder.makerFee
- // .times(makerAssetFilledAmount)
- // .dividedToIntegerBy(signedOrder.makerAssetAmount);
- // const takerFeePaid = signedOrder.takerFee
- // .times(makerAssetFilledAmount)
- // .dividedToIntegerBy(signedOrder.makerAssetAmount);
- // expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- // erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
- // );
- // expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- // erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
- // );
- // expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
- // erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
- // );
- // expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- // erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
- // );
- // expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- // erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
- // );
- // expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
- // erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
- // );
- // expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- // erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
- // );
- });
- });
+ describe.only('Fills orders', () => test(allOrderScenarios));
});
diff --git a/packages/order-utils/src/exchange_transfer_simulator.ts b/packages/order-utils/src/exchange_transfer_simulator.ts
index 32d53d6a2..cac4af243 100644
--- a/packages/order-utils/src/exchange_transfer_simulator.ts
+++ b/packages/order-utils/src/exchange_transfer_simulator.ts
@@ -1,7 +1,8 @@
-import { ExchangeContractErrs } from '@0xproject/types';
+import { AssetProxyId, ExchangeContractErrs } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
+import { assetProxyUtils } from './asset_proxy_utils';
import { constants } from './constants';
import { TradeSide, TransferType } from './types';
@@ -89,8 +90,13 @@ export class ExchangeTransferSimulator {
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
+ const assetProxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ const isERC721Asset = assetProxyId === AssetProxyId.ERC721;
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, userAddress);
- if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ // HACK: This code assumes that all tokens with an UNLIMITED_ALLOWANCE_IN_BASE_UNITS set,
+ // are UnlimitedAllowanceTokens. This is however not true, it just so happens that all
+ // DummyERC20Tokens we use in tests are.
+ if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS) && !isERC721Asset) {
this._store.setProxyAllowance(assetData, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
}
diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts
index 40f235da7..dbb65de59 100644
--- a/packages/order-utils/src/order_state_utils.ts
+++ b/packages/order-utils/src/order_state_utils.ts
@@ -7,43 +7,85 @@ import {
SignedOrder,
} from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
-import { assetProxyUtils } from './asset_proxy_utils';
+import { constants } from './constants';
import { orderHashUtils } from './order_hash';
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
+interface SidedOrderRelevantState {
+ isMakerSide: boolean;
+ traderBalance: BigNumber;
+ traderProxyAllowance: BigNumber;
+ traderFeeBalance: BigNumber;
+ traderFeeProxyAllowance: BigNumber;
+ filledTakerAssetAmount: BigNumber;
+ remainingFillableAssetAmount: BigNumber;
+}
+
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
export class OrderStateUtils {
private _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher;
private _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
- private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
- const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(orderRelevantState.filledTakerAssetAmount);
+ private static _validateIfOrderIsValid(
+ signedOrder: SignedOrder,
+ sidedOrderRelevantState: SidedOrderRelevantState,
+ ): void {
+ const isMakerSide = sidedOrderRelevantState.isMakerSide;
+ const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(
+ sidedOrderRelevantState.filledTakerAssetAmount,
+ );
if (availableTakerAssetAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
- if (orderRelevantState.makerBalance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
+ if (sidedOrderRelevantState.traderBalance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerBalance
+ : ExchangeContractErrs.InsufficientTakerBalance,
+ );
}
- if (orderRelevantState.makerProxyAllowance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
+ if (sidedOrderRelevantState.traderProxyAllowance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerAllowance
+ : ExchangeContractErrs.InsufficientTakerAllowance,
+ );
}
if (!signedOrder.makerFee.eq(0)) {
- if (orderRelevantState.makerFeeBalance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
+ if (sidedOrderRelevantState.traderFeeBalance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerFeeBalance
+ : ExchangeContractErrs.InsufficientTakerFeeBalance,
+ );
}
- if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
- throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
+ if (sidedOrderRelevantState.traderFeeProxyAllowance.eq(0)) {
+ throw new Error(
+ isMakerSide
+ ? ExchangeContractErrs.InsufficientMakerFeeAllowance
+ : ExchangeContractErrs.InsufficientTakerFeeAllowance,
+ );
}
}
- const minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
- .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
- .dividedBy(signedOrder.makerAssetAmount);
+
+ let minFillableTakerAssetAmountWithinNoRoundingErrorRange;
+ if (isMakerSide) {
+ minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
+ .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
+ .dividedBy(signedOrder.makerAssetAmount);
+ } else {
+ minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.makerAssetAmount
+ .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
+ .dividedBy(signedOrder.takerAssetAmount);
+ }
+
if (
- orderRelevantState.remainingFillableTakerAssetAmount.lessThan(
+ sidedOrderRelevantState.remainingFillableAssetAmount.lessThan(
minFillableTakerAssetAmountWithinNoRoundingErrorRange,
)
) {
@@ -57,11 +99,20 @@ export class OrderStateUtils {
this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
}
- public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
- const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
+ public async getOpenOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
+ const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const sidedOrderRelevantState = {
+ isMakerSide: true,
+ traderBalance: orderRelevantState.makerBalance,
+ traderProxyAllowance: orderRelevantState.makerProxyAllowance,
+ traderFeeBalance: orderRelevantState.makerFeeBalance,
+ traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance,
+ filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount,
+ remainingFillableAssetAmount: orderRelevantState.remainingFillableMakerAssetAmount,
+ };
try {
- OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
+ OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState);
const orderState: OrderStateValid = {
isValid: true,
orderHash,
@@ -77,63 +128,152 @@ export class OrderStateUtils {
return orderState;
}
}
- public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
- const zrxTokenAddress = this._orderFilledCancelledFetcher.getZRXTokenAddress();
- const makerProxyData = assetProxyUtils.decodeERC20AssetData(signedOrder.makerAssetData);
- const makerAssetAddress = makerProxyData.tokenAddress;
- const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
- const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
- makerAssetAddress,
- signedOrder.makerAddress,
+ public async getOpenOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
+ const isMaker = true;
+ const sidedOrderRelevantState = await this._getSidedOrderRelevantStateAsync(
+ isMaker,
+ signedOrder,
+ signedOrder.takerAddress,
+ );
+ const remainingFillableTakerAssetAmount = sidedOrderRelevantState.remainingFillableAssetAmount
+ .times(signedOrder.takerAssetAmount)
+ .dividedToIntegerBy(signedOrder.makerAssetAmount);
+
+ const orderRelevantState = {
+ makerBalance: sidedOrderRelevantState.traderBalance,
+ makerProxyAllowance: sidedOrderRelevantState.traderProxyAllowance,
+ makerFeeBalance: sidedOrderRelevantState.traderFeeBalance,
+ makerFeeProxyAllowance: sidedOrderRelevantState.traderFeeProxyAllowance,
+ filledTakerAssetAmount: sidedOrderRelevantState.filledTakerAssetAmount,
+ remainingFillableMakerAssetAmount: sidedOrderRelevantState.remainingFillableAssetAmount,
+ remainingFillableTakerAssetAmount,
+ };
+ return orderRelevantState;
+ }
+ public async getMaxFillableTakerAssetAmountAsync(
+ signedOrder: SignedOrder,
+ takerAddress: string,
+ ): Promise<BigNumber> {
+ // Get max fillable amount for an order, considering the makers ability to fill
+ let isMaker = true;
+ const orderRelevantMakerState = await this._getSidedOrderRelevantStateAsync(
+ isMaker,
+ signedOrder,
+ signedOrder.takerAddress,
+ );
+ const remainingFillableTakerAssetAmountGivenMakersStatus = orderRelevantMakerState.remainingFillableAssetAmount;
+
+ // Get max fillable amount for an order, considering the takers ability to fill
+ isMaker = false;
+ const orderRelevantTakerState = await this._getSidedOrderRelevantStateAsync(isMaker, signedOrder, takerAddress);
+ const remainingFillableTakerAssetAmountGivenTakersStatus = orderRelevantTakerState.remainingFillableAssetAmount;
+
+ // The min of these two in the actualy max fillable by either party
+ const fillableTakerAssetAmount = BigNumber.min([
+ remainingFillableTakerAssetAmountGivenMakersStatus,
+ remainingFillableTakerAssetAmountGivenTakersStatus,
+ ]);
+
+ return fillableTakerAssetAmount;
+ }
+ public async getMaxFillableTakerAssetAmountForFailingOrderAsync(
+ signedOrder: SignedOrder,
+ takerAddress: string,
+ ): Promise<BigNumber> {
+ // Get min of taker balance & allowance
+ const takerAssetBalanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
+ signedOrder.takerAssetData,
+ takerAddress,
);
- const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- makerAssetAddress,
- signedOrder.makerAddress,
+ const takerAssetAllowanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ signedOrder.takerAssetData,
+ takerAddress,
);
- const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
+ const minTakerAssetAmount = BigNumber.min([takerAssetBalanceOfTaker, takerAssetAllowanceOfTaker]);
+
+ // get remainingFillAmount
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
+ const remainingFillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerAssetAmount);
+
+ if (minTakerAssetAmount.gte(remainingFillTakerAssetAmount)) {
+ return remainingFillTakerAssetAmount;
+ } else {
+ return minTakerAssetAmount;
+ }
+ }
+ private async _getSidedOrderRelevantStateAsync(
+ isMakerSide: boolean,
+ signedOrder: SignedOrder,
+ takerAddress: string,
+ ): Promise<SidedOrderRelevantState> {
+ let traderAddress;
+ let assetData;
+ let assetAmount;
+ let feeAmount;
+ if (isMakerSide) {
+ traderAddress = signedOrder.makerAddress;
+ assetData = signedOrder.makerAssetData;
+ assetAmount = signedOrder.makerAssetAmount;
+ feeAmount = signedOrder.makerFee;
+ } else {
+ traderAddress = takerAddress;
+ assetData = signedOrder.takerAssetData;
+ assetAmount = signedOrder.takerAssetAmount;
+ feeAmount = signedOrder.takerFee;
+ }
+ const zrxAssetData = this._orderFilledCancelledFetcher.getZRXAssetData();
+ const isAssetZRX = assetData === zrxAssetData;
+
+ const traderBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress);
+ const traderProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ assetData,
+ traderAddress,
+ );
+ const traderFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
+ zrxAssetData,
+ traderAddress,
);
- const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
+ const traderFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ zrxAssetData,
+ traderAddress,
);
+
+ const transferrableTraderAssetAmount = BigNumber.min([traderProxyAllowance, traderBalance]);
+ const transferrableFeeAssetAmount = BigNumber.min([traderFeeProxyAllowance, traderFeeBalance]);
+
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
- const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
const totalMakerAssetAmount = signedOrder.makerAssetAmount;
const totalTakerAssetAmount = signedOrder.takerAssetAmount;
+ const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
const remainingTakerAssetAmount = isOrderCancelled
? new BigNumber(0)
: totalTakerAssetAmount.minus(filledTakerAssetAmount);
- const remainingMakerAssetAmount = remainingTakerAssetAmount
- .times(totalMakerAssetAmount)
- .dividedToIntegerBy(totalTakerAssetAmount);
- const transferrableMakerAssetAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
- const transferrableFeeAssetAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
-
- const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxTokenAddress);
- const isMakerAssetZRX = signedOrder.makerAssetData === zrxAssetData;
+ const remainingMakerAssetAmount = remainingTakerAssetAmount.eq(0)
+ ? new BigNumber(0)
+ : remainingTakerAssetAmount.times(totalMakerAssetAmount).dividedToIntegerBy(totalTakerAssetAmount);
+ const remainingAssetAmount = isMakerSide ? remainingMakerAssetAmount : remainingTakerAssetAmount;
+
const remainingFillableCalculator = new RemainingFillableCalculator(
- signedOrder.makerFee,
- signedOrder.makerAssetAmount,
- isMakerAssetZRX,
- transferrableMakerAssetAmount,
+ feeAmount,
+ assetAmount,
+ isAssetZRX,
+ transferrableTraderAssetAmount,
transferrableFeeAssetAmount,
- remainingMakerAssetAmount,
+ remainingAssetAmount,
);
- const remainingFillableMakerAssetAmount = remainingFillableCalculator.computeRemainingFillable();
- const remainingFillableTakerAssetAmount = remainingFillableMakerAssetAmount
- .times(signedOrder.takerAssetAmount)
- .dividedToIntegerBy(signedOrder.makerAssetAmount);
- const orderRelevantState = {
- makerBalance,
- makerProxyAllowance,
- makerFeeBalance,
- makerFeeProxyAllowance,
+ const remainingFillableAssetAmount = remainingFillableCalculator.computeRemainingFillable();
+
+ const sidedOrderRelevantState = {
+ isMakerSide,
+ traderBalance,
+ traderProxyAllowance,
+ traderFeeBalance,
+ traderFeeProxyAllowance,
filledTakerAssetAmount,
- remainingFillableMakerAssetAmount,
- remainingFillableTakerAssetAmount,
+ remainingFillableAssetAmount,
};
- return orderRelevantState;
+ return sidedOrderRelevantState;
}
}