aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts/src')
-rw-r--r--packages/contracts/src/abstract/abstract_asset_wrapper.ts10
-rw-r--r--packages/contracts/src/utils/asset_wrapper.ts32
-rw-r--r--packages/contracts/src/utils/core_combinatorial_utils.ts481
-rw-r--r--packages/contracts/src/utils/erc20_wrapper.ts39
-rw-r--r--packages/contracts/src/utils/erc721_wrapper.ts35
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts8
-rw-r--r--packages/contracts/src/utils/new_order_factory.ts277
-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_order_filled_cancelled_fetcher.ts24
-rw-r--r--packages/contracts/src/utils/types.ts55
11 files changed, 981 insertions, 6 deletions
diff --git a/packages/contracts/src/abstract/abstract_asset_wrapper.ts b/packages/contracts/src/abstract/abstract_asset_wrapper.ts
new file mode 100644
index 000000000..d7ab58fea
--- /dev/null
+++ b/packages/contracts/src/abstract/abstract_asset_wrapper.ts
@@ -0,0 +1,10 @@
+import { BigNumber } from '@0xproject/utils';
+
+export abstract class AbstractAssetWrapper {
+ public abstract getProxyId(): number;
+ public abstract async setBalancesAndAllowancesAsync(): Promise<void>;
+ public abstract async getBalanceAsync(owner: string, assetData: string): Promise<BigNumber>;
+ public abstract async getProxyAllowanceAsync(owner: string, assetData: string): Promise<BigNumber>;
+ public abstract getTokenOwnerAddresses(): string[];
+ public abstract getTokenAddresses(): string[];
+}
diff --git a/packages/contracts/src/utils/asset_wrapper.ts b/packages/contracts/src/utils/asset_wrapper.ts
new file mode 100644
index 000000000..4c345aa30
--- /dev/null
+++ b/packages/contracts/src/utils/asset_wrapper.ts
@@ -0,0 +1,32 @@
+import { assetProxyUtils } from '@0xproject/order-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { AbstractAssetWrapper } from '../abstract/abstract_asset_wrapper';
+
+interface ProxyIdToAssetWrappers {
+ [proxyId: number]: AbstractAssetWrapper;
+}
+
+export class AssetWrapper {
+ private _proxyIdToAssetWrappers: ProxyIdToAssetWrappers;
+ constructor(assetWrappers: AbstractAssetWrapper[]) {
+ this._proxyIdToAssetWrappers = {};
+ _.each(assetWrappers, assetWrapper => {
+ const proxyId = assetWrapper.getProxyId();
+ this._proxyIdToAssetWrappers[proxyId] = assetWrapper;
+ });
+ }
+ public async getBalanceAsync(owner: string, assetData: string): Promise<BigNumber> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ const assetWrapper = this._proxyIdToAssetWrappers[proxyId];
+ const balance = await assetWrapper.getBalanceAsync(owner, assetData);
+ return balance;
+ }
+ public async getProxyAllowanceAsync(owner: string, assetData: string): Promise<BigNumber> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ const assetWrapper = this._proxyIdToAssetWrappers[proxyId];
+ const balance = await assetWrapper.getProxyAllowanceAsync(owner, assetData);
+ return balance;
+ }
+}
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..4ce23d450
--- /dev/null
+++ b/packages/contracts/src/utils/core_combinatorial_utils.ts
@@ -0,0 +1,481 @@
+import {
+ assetProxyUtils,
+ BalanceAndProxyAllowanceLazyStore,
+ ExchangeTransferSimulator,
+ orderHashUtils,
+ OrderStateUtils,
+ OrderValidationUtils,
+} from '@0xproject/order-utils';
+import { AssetProxyId, Order, SignatureType, SignedOrder } from '@0xproject/types';
+import { BigNumber, errorUtils } 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,
+ TakerAssetFillAmountScenario,
+ TakerScenario,
+ FillScenario,
+} 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 takerScenarios = [TakerScenario.Unspecified];
+ const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress];
+ const makerAssetAmountScenario = [OrderAmountScenario.Large];
+ const takerAssetAmountScenario = [OrderAmountScenario.Large];
+ const makerFeeScenario = [OrderAmountScenario.Large];
+ const takerFeeScenario = [OrderAmountScenario.Large];
+ 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([
+ takerScenarios,
+ feeRecipientScenarios,
+ makerAssetAmountScenario,
+ takerAssetAmountScenario,
+ makerFeeScenario,
+ takerFeeScenario,
+ expirationTimeSecondsScenario,
+ makerAssetDataScenario,
+ takerAssetDataScenario,
+ ]);
+
+ const orderScenarios = _.map(orderScenarioArrays, orderScenarioArray => {
+ const orderScenario: OrderScenario = {
+ takerScenario: orderScenarioArray[0] as TakerScenario,
+ feeRecipientScenario: orderScenarioArray[1] as FeeRecipientAddressScenario,
+ makerAssetAmountScenario: orderScenarioArray[2] as OrderAmountScenario,
+ takerAssetAmountScenario: orderScenarioArray[3] as OrderAmountScenario,
+ makerFeeScenario: orderScenarioArray[4] as OrderAmountScenario,
+ takerFeeScenario: orderScenarioArray[5] as OrderAmountScenario,
+ expirationTimeSecondsScenario: orderScenarioArray[6] as ExpirationTimeSecondsScenario,
+ makerAssetDataScenario: orderScenarioArray[7] as AssetDataScenario,
+ takerAssetDataScenario: orderScenarioArray[8] 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(provider: Provider, fillScenario: FillScenario): Promise<void> {
+ // 1. Generate order
+ const order = this.orderFactory.generateOrder(fillScenario.orderScenario);
+
+ // 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
+ // 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);
+
+ const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync(
+ signedOrder,
+ this.takerAddress,
+ );
+
+ let takerAssetFillAmount;
+ const takerAssetFillAmountScenario = fillScenario.takerAssetFillAmountScenario;
+ switch (takerAssetFillAmountScenario) {
+ case TakerAssetFillAmountScenario.Zero:
+ takerAssetFillAmount = new BigNumber(0);
+ break;
+
+ case TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount:
+ takerAssetFillAmount = fillableTakerAssetAmount;
+ break;
+
+ case TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount:
+ takerAssetFillAmount = fillableTakerAssetAmount.add(1);
+ break;
+
+ case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount:
+ const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData);
+ const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.makerAssetData);
+ const isEitherAssetERC721 =
+ takerAssetProxyId === ERC721_PROXY_ID || makerAssetProxyId === 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.',
+ );
+ }
+ takerAssetFillAmount = fillableTakerAssetAmount.div(2).floor();
+ break;
+
+ default:
+ 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,
+ );
+ }
+ 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 alreadyFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
+ const remainingTakerAmountToFill = signedOrder.takerAssetAmount.minus(alreadyFilledTakerAmount);
+ const expFilledTakerAmount = takerAssetFillAmount.gt(remainingTakerAmountToFill)
+ ? remainingTakerAmountToFill
+ : alreadyFilledTakerAmount.add(takerAssetFillAmount);
+
+ const expFilledMakerAmount = orderUtils.getPartialAmount(
+ expFilledTakerAmount,
+ 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(
+ expFilledTakerAmount,
+ '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/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts
index 91c9d50b7..2d367ae1c 100644
--- a/packages/contracts/src/utils/erc20_wrapper.ts
+++ b/packages/contracts/src/utils/erc20_wrapper.ts
@@ -1,3 +1,4 @@
+import { assetProxyUtils } from '@0xproject/order-utils';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
@@ -18,6 +19,7 @@ export class ERC20Wrapper {
private _provider: Provider;
private _dummyTokenContracts: DummyERC20TokenContract[];
private _proxyContract?: ERC20ProxyContract;
+ private _proxyIdIfExists?: number;
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
this._dummyTokenContracts = [];
this._web3Wrapper = new Web3Wrapper(provider);
@@ -25,8 +27,11 @@ export class ERC20Wrapper {
this._tokenOwnerAddresses = tokenOwnerAddresses;
this._contractOwnerAddress = contractOwnerAddress;
}
- public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> {
- for (let i = 0; i < constants.NUM_DUMMY_ERC20_TO_DEPLOY; i++) {
+ public async deployDummyTokensAsync(num?: number, decimals?: BigNumber): Promise<DummyERC20TokenContract[]> {
+ // TODO(fabio): Remove and refactor all tests
+ const finalNum = _.isUndefined(num) ? constants.NUM_DUMMY_ERC20_TO_DEPLOY : num;
+ const finalDecimals = _.isUndefined(decimals) ? constants.DUMMY_TOKEN_DECIMALS : decimals;
+ for (let i = 0; i < finalNum; i++) {
this._dummyTokenContracts.push(
await DummyERC20TokenContract.deployFrom0xArtifactAsync(
artifacts.DummyERC20Token,
@@ -34,7 +39,7 @@ export class ERC20Wrapper {
txDefaults,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
- constants.DUMMY_TOKEN_DECIMALS,
+ finalDecimals,
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
),
);
@@ -47,8 +52,13 @@ export class ERC20Wrapper {
this._provider,
txDefaults,
);
+ this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
return this._proxyContract;
}
+ public getProxyId(): number {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as number;
+ }
public async setBalancesAndAllowancesAsync(): Promise<void> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
@@ -73,6 +83,29 @@ export class ERC20Wrapper {
}
}
}
+ public async getBalanceAsync(owner: string, assetData: string): Promise<BigNumber> {
+ const erc20ProxyData = assetProxyUtils.decodeERC20AssetData(assetData);
+ const tokenAddress = erc20ProxyData.tokenAddress;
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ const balance = new BigNumber(await tokenContractIfExists.balanceOf.callAsync(owner));
+ return balance;
+ }
+ public async getProxyAllowanceAsync(owner: string, assetData: string): Promise<BigNumber> {
+ const erc20ProxyData = assetProxyUtils.decodeERC20AssetData(assetData);
+ const tokenAddress = erc20ProxyData.tokenAddress;
+ this._validateProxyContractExistsOrThrow();
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ const allowance = new BigNumber(
+ await tokenContractIfExists.allowance.callAsync(owner, (this._proxyContract as ERC20ProxyContract).address),
+ );
+ return allowance;
+ }
public async getBalancesAsync(): Promise<ERC20BalancesByOwner> {
this._validateDummyTokenContractsExistOrThrow();
const balancesByOwner: ERC20BalancesByOwner = {};
diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts
index b20d9acd2..6a6f10fa7 100644
--- a/packages/contracts/src/utils/erc721_wrapper.ts
+++ b/packages/contracts/src/utils/erc721_wrapper.ts
@@ -1,4 +1,4 @@
-import { generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
@@ -19,6 +19,7 @@ export class ERC721Wrapper {
private _provider: Provider;
private _dummyTokenContracts: DummyERC721TokenContract[];
private _proxyContract?: ERC721ProxyContract;
+ private _proxyIdIfExists?: number;
private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
this._web3Wrapper = new Web3Wrapper(provider);
@@ -47,8 +48,13 @@ export class ERC721Wrapper {
this._provider,
txDefaults,
);
+ this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
return this._proxyContract;
}
+ public getProxyId(): number {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as number;
+ }
public async setBalancesAndAllowancesAsync(): Promise<void> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
@@ -85,6 +91,33 @@ export class ERC721Wrapper {
}
}
}
+ public async getBalanceAsync(owner: string, assetData: string): Promise<BigNumber> {
+ const erc721ProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const tokenAddress = erc721ProxyData.tokenAddress;
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ const tokenId = erc721ProxyData.tokenId;
+ const tokenOwner = await tokenContractIfExists.ownerOf.callAsync(tokenId);
+ const balance = tokenOwner === owner ? new BigNumber(1) : new BigNumber(0);
+ return balance;
+ }
+ public async getProxyAllowanceAsync(owner: string, assetData: string): Promise<BigNumber> {
+ const erc721ProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const tokenAddress = erc721ProxyData.tokenAddress;
+ this._validateProxyContractExistsOrThrow();
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ const operator = (this._proxyContract as ERC721ProxyContract).address;
+ const didApproveAll = await tokenContractIfExists.isApprovedForAll.callAsync(owner, operator);
+ const tokenId = erc721ProxyData.tokenId;
+ const allowedAddress = await tokenContractIfExists.getApproved.callAsync(tokenId);
+ const allowance = allowedAddress === operator || didApproveAll ? new BigNumber(1) : new BigNumber(0);
+ return allowance;
+ }
public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> {
this._validateDummyTokenContractsExistOrThrow();
this._validateBalancesAndAllowancesSetOrThrow();
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
index 4cc8f0b89..38dcb891a 100644
--- a/packages/contracts/src/utils/exchange_wrapper.ts
+++ b/packages/contracts/src/utils/exchange_wrapper.ts
@@ -33,8 +33,8 @@ export class ExchangeWrapper {
params.signature,
{ from },
);
- const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
- return tx;
+ const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return txReceipt;
}
public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createCancel(signedOrder);
@@ -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
new file mode 100644
index 000000000..7de7be3bd
--- /dev/null
+++ b/packages/contracts/src/utils/new_order_factory.ts
@@ -0,0 +1,277 @@
+import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { Order } from '@0xproject/types';
+import { BigNumber, errorUtils } from '@0xproject/utils';
+
+import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token';
+
+import { constants } from './constants';
+import {
+ AssetDataScenario,
+ ERC721TokenIdsByOwner,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ OrderAmountScenario,
+ OrderScenario,
+ TakerScenario,
+} from './types';
+
+const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10000000000000000000);
+const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5000000000000000000);
+const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100000000000000000);
+const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50000000000000000);
+const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1000000);
+const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500000);
+const ONE_NFT_UNIT = new BigNumber(1);
+
+export class NewOrderFactory {
+ private _userAddresses: string[];
+ private _zrxAddress: string;
+ private _nonZrxERC20EighteenDecimalTokenAddresses: string[];
+ private _erc20FiveDecimalTokenAddresses: string[];
+ private _erc721Token: DummyERC721TokenContract;
+ private _erc721Balances: ERC721TokenIdsByOwner;
+ private _exchangeAddress: string;
+ constructor(
+ userAddresses: string[],
+ zrxAddress: string,
+ nonZrxERC20EighteenDecimalTokenAddresses: string[],
+ erc20FiveDecimalTokenAddresses: string[],
+ erc721Token: DummyERC721TokenContract,
+ erc721Balances: ERC721TokenIdsByOwner,
+ exchangeAddress: string,
+ ) {
+ this._userAddresses = userAddresses;
+ this._zrxAddress = zrxAddress;
+ this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses;
+ this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses;
+ this._erc721Token = erc721Token;
+ this._erc721Balances = erc721Balances;
+ this._exchangeAddress = exchangeAddress;
+ }
+ public generateOrder(orderScenario: OrderScenario): Order {
+ const makerAddress = this._userAddresses[1];
+ let takerAddress = this._userAddresses[2];
+ const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address];
+ const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address];
+ let feeRecipientAddress;
+ let makerAssetAmount;
+ let takerAssetAmount;
+ let makerFee;
+ let takerFee;
+ let expirationTimeSeconds;
+ let makerAssetData;
+ let takerAssetData;
+
+ switch (orderScenario.feeRecipientScenario) {
+ case FeeRecipientAddressScenario.BurnAddress:
+ feeRecipientAddress = constants.NULL_ADDRESS;
+ break;
+ case FeeRecipientAddressScenario.EthUserAddress:
+ feeRecipientAddress = this._userAddresses[4];
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario);
+ }
+
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
+ break;
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(
+ this._nonZrxERC20EighteenDecimalTokenAddresses[0],
+ );
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ makerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]);
+ break;
+ case AssetDataScenario.ERC721:
+ makerAssetData = assetProxyUtils.encodeERC721AssetData(
+ this._erc721Token.address,
+ erc721MakerAssetIds[0],
+ );
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
+ }
+
+ switch (orderScenario.takerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
+ break;
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(
+ this._nonZrxERC20EighteenDecimalTokenAddresses[1],
+ );
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ takerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]);
+ break;
+ case AssetDataScenario.ERC721:
+ takerAssetData = assetProxyUtils.encodeERC721AssetData(
+ this._erc721Token.address,
+ erc721TakerAssetIds[0],
+ );
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
+ }
+
+ switch (orderScenario.makerAssetAmountScenario) {
+ case OrderAmountScenario.Large:
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ makerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ makerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
+ }
+ break;
+ case OrderAmountScenario.Small:
+ switch (orderScenario.makerAssetDataScenario) {
+ case AssetDataScenario.ZRXFeeToken:
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ makerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
+ }
+ break;
+ case OrderAmountScenario.Zero:
+ makerAssetAmount = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.makerAssetAmountScenario);
+ }
+
+ switch (orderScenario.takerAssetAmountScenario) {
+ case OrderAmountScenario.Large:
+ switch (orderScenario.takerAssetDataScenario) {
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ case AssetDataScenario.ZRXFeeToken:
+ takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ takerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ takerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
+ }
+ break;
+ case OrderAmountScenario.Small:
+ switch (orderScenario.takerAssetDataScenario) {
+ case AssetDataScenario.ERC20NonZRXEighteenDecimals:
+ case AssetDataScenario.ZRXFeeToken:
+ takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case AssetDataScenario.ERC20FiveDecimals:
+ takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS;
+ break;
+ case AssetDataScenario.ERC721:
+ takerAssetAmount = ONE_NFT_UNIT;
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
+ }
+ break;
+ case OrderAmountScenario.Zero:
+ takerAssetAmount = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.takerAssetAmountScenario);
+ }
+
+ switch (orderScenario.makerFeeScenario) {
+ case OrderAmountScenario.Large:
+ makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAmountScenario.Small:
+ makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAmountScenario.Zero:
+ makerFee = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.makerFeeScenario);
+ }
+
+ switch (orderScenario.takerFeeScenario) {
+ case OrderAmountScenario.Large:
+ takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAmountScenario.Small:
+ takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAmountScenario.Zero:
+ takerFee = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.takerFeeScenario);
+ }
+
+ switch (orderScenario.expirationTimeSecondsScenario) {
+ case ExpirationTimeSecondsScenario.InFuture:
+ expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite
+ break;
+ case ExpirationTimeSecondsScenario.InPast:
+ expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'ExpirationTimeSecondsScenario',
+ orderScenario.expirationTimeSecondsScenario,
+ );
+ }
+
+ switch (orderScenario.takerScenario) {
+ case TakerScenario.CorrectlySpecified:
+ break; // noop since takerAddress is already specified
+
+ case TakerScenario.IncorrectlySpecified:
+ const notTaker = this._userAddresses[3];
+ takerAddress = notTaker;
+ break;
+
+ case TakerScenario.Unspecified:
+ takerAddress = constants.NULL_ADDRESS;
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('TakerScenario', orderScenario.takerScenario);
+ }
+
+ const order: Order = {
+ senderAddress: constants.NULL_ADDRESS,
+ makerAddress,
+ takerAddress,
+ makerFee,
+ takerFee,
+ makerAssetAmount,
+ takerAssetAmount,
+ makerAssetData,
+ takerAssetData,
+ salt: generatePseudoRandomSalt(),
+ exchangeAddress: this._exchangeAddress,
+ feeRecipientAddress,
+ expirationTimeSeconds,
+ };
+
+ return order;
+ }
+}
diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts
index a9f994d80..019f6e74b 100644
--- a/packages/contracts/src/utils/order_utils.ts
+++ b/packages/contracts/src/utils/order_utils.ts
@@ -5,6 +5,13 @@ import { constants } from './constants';
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_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 bb8c12088..318ea91f9 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -150,3 +150,58 @@ export interface MatchOrder {
leftSignature: string;
rightSignature: string;
}
+
+// Combinatorial testing types
+
+export enum FeeRecipientAddressScenario {
+ BurnAddress = 'BURN_ADDRESS',
+ EthUserAddress = 'ETH_USER_ADDRESS',
+}
+
+export enum OrderAmountScenario {
+ Zero = 'ZERO',
+ Large = 'LARGE',
+ Small = 'SMALL',
+}
+
+export enum TakerScenario {
+ CorrectlySpecified = 'CORRECTLY_SPECFIED',
+ IncorrectlySpecified = 'INCORRECTLY_SPECFIED',
+ Unspecified = 'UNSPECIFIED',
+}
+
+export enum ExpirationTimeSecondsScenario {
+ InPast = 'IN_PAST',
+ InFuture = 'IN_FUTURE',
+}
+
+export enum AssetDataScenario {
+ ERC721 = 'ERC721',
+ ZRXFeeToken = 'ZRX_FEE_TOKEN',
+ ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS',
+ ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS',
+}
+
+export enum TakerAssetFillAmountScenario {
+ Zero = 'ZERO',
+ GreaterThanRemainingFillableTakerAssetAmount = 'GREATER_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
+ LessThanRemainingFillableTakerAssetAmount = 'LESS_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
+ ExactlyRemainingFillableTakerAssetAmount = 'EXACTLY_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
+}
+
+export interface OrderScenario {
+ takerScenario: TakerScenario;
+ feeRecipientScenario: FeeRecipientAddressScenario;
+ makerAssetAmountScenario: OrderAmountScenario;
+ takerAssetAmountScenario: OrderAmountScenario;
+ makerFeeScenario: OrderAmountScenario;
+ takerFeeScenario: OrderAmountScenario;
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario;
+ makerAssetDataScenario: AssetDataScenario;
+ takerAssetDataScenario: AssetDataScenario;
+}
+
+export interface FillScenario {
+ orderScenario: OrderScenario;
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario;
+}