aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-06-28 17:04:16 +0800
committerGitHub <noreply@github.com>2018-06-28 17:04:16 +0800
commit988a3347582cd812aadeaa0cdaef67389c8d833d (patch)
treeb3067914037f0423480be297c860b4c3adaf2e3a
parent57d5fbfbd8d15d96061687e5badcecec4ffdd713 (diff)
parent4fe626904337290774e39f3aba143fa776785269 (diff)
downloaddexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.tar
dexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.tar.gz
dexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.tar.bz2
dexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.tar.lz
dexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.tar.xz
dexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.tar.zst
dexon-sol-tools-988a3347582cd812aadeaa0cdaef67389c8d833d.zip
Merge pull request #707 from 0xProject/feature/combinatorial-testing
OrderFill Declarative Testing & Combinatorial Tests [Merge after #760]
-rw-r--r--packages/contracts/src/abstract/abstract_asset_wrapper.ts3
-rw-r--r--packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol16
-rw-r--r--packages/contracts/src/utils/asset_wrapper.ts217
-rw-r--r--packages/contracts/src/utils/core_combinatorial_utils.ts801
-rw-r--r--packages/contracts/src/utils/erc20_wrapper.ts55
-rw-r--r--packages/contracts/src/utils/erc721_wrapper.ts119
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts8
-rw-r--r--packages/contracts/src/utils/order_factory_from_scenario.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.ts77
-rw-r--r--packages/contracts/test/asset_proxy/proxies.ts10
-rw-r--r--packages/contracts/test/exchange/core.ts501
-rw-r--r--packages/contracts/test/exchange/dispatcher.ts6
-rw-r--r--packages/contracts/test/exchange/fill_order.ts306
-rw-r--r--packages/contracts/test/exchange/match_orders.ts8
-rw-r--r--packages/contracts/test/exchange/transactions.ts13
-rw-r--r--packages/contracts/test/exchange/wrapper.ts10
-rw-r--r--packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts2
-rw-r--r--packages/order-utils/src/exchange_transfer_simulator.ts3
-rw-r--r--packages/order-utils/src/index.ts1
-rw-r--r--packages/order-utils/src/order_state_utils.ts267
-rw-r--r--packages/order-utils/src/order_validation_utils.ts193
-rw-r--r--packages/order-utils/src/remaining_fillable_calculator.ts6
-rw-r--r--packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts4
-rw-r--r--packages/order-utils/src/utils.ts7
-rw-r--r--packages/order-utils/test/exchange_transfer_simulator_test.ts25
-rw-r--r--packages/order-watcher/test/global_hooks.ts2
29 files changed, 2276 insertions, 711 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..4b56a8502
--- /dev/null
+++ b/packages/contracts/src/abstract/abstract_asset_wrapper.ts
@@ -0,0 +1,3 @@
+export abstract class AbstractAssetWrapper {
+ public abstract getProxyId(): string;
+}
diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
index 5503eb2f2..78ea96447 100644
--- a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
+++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
@@ -56,4 +56,20 @@ contract DummyERC721Token is
);
_mint(to, tokenId);
}
+
+ /**
+ * @dev Function to burn a token
+ * @dev Reverts if the given token ID doesn't exist
+ * @param tokenId uint256 ID of the token to be minted by the msg.sender
+ */
+ function burn(address owner, uint256 tokenId)
+ public
+ onlyOwner
+ {
+ require(
+ exists(tokenId),
+ "Token with tokenId does not exist."
+ );
+ _burn(owner, tokenId);
+ }
}
diff --git a/packages/contracts/src/utils/asset_wrapper.ts b/packages/contracts/src/utils/asset_wrapper.ts
new file mode 100644
index 000000000..a7f91f413
--- /dev/null
+++ b/packages/contracts/src/utils/asset_wrapper.ts
@@ -0,0 +1,217 @@
+import { assetProxyUtils } from '@0xproject/order-utils';
+import { AssetProxyId } from '@0xproject/types';
+import { BigNumber, errorUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { AbstractAssetWrapper } from '../abstract/abstract_asset_wrapper';
+
+import { constants } from './constants';
+import { ERC20Wrapper } from './erc20_wrapper';
+import { ERC721Wrapper } from './erc721_wrapper';
+
+interface ProxyIdToAssetWrappers {
+ [proxyId: string]: AbstractAssetWrapper;
+}
+
+/**
+ * This class abstracts away the differences between ERC20 and ERC721 tokens so that
+ * the logic that uses it does not need to care what standard a token belongs to.
+ */
+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(userAddress: string, assetData: string): Promise<BigNumber> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ const balance = await erc20Wrapper.getBalanceAsync(userAddress, assetData);
+ return balance;
+ }
+ case AssetProxyId.ERC721: {
+ const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const isOwner = await assetWrapper.isOwnerAsync(
+ userAddress,
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ const balance = isOwner ? new BigNumber(1) : new BigNumber(0);
+ return balance;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+ public async setBalanceAsync(userAddress: string, assetData: string, desiredBalance: BigNumber): Promise<void> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance);
+ return;
+ }
+ case AssetProxyId.ERC721: {
+ if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) {
+ throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`);
+ }
+ const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const doesTokenExist = erc721Wrapper.doesTokenExistAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (!doesTokenExist && desiredBalance.eq(1)) {
+ await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
+ return;
+ } else if (!doesTokenExist && desiredBalance.eq(0)) {
+ return; // noop
+ }
+ const tokenOwner = await erc721Wrapper.ownerOfAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (userAddress !== tokenOwner && desiredBalance.eq(1)) {
+ await erc721Wrapper.transferFromAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ tokenOwner,
+ userAddress,
+ );
+ } else if (tokenOwner === userAddress && desiredBalance.eq(0)) {
+ // Burn token
+ await erc721Wrapper.burnAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
+ return;
+ } else if (
+ (userAddress !== tokenOwner && desiredBalance.eq(0)) ||
+ (tokenOwner === userAddress && desiredBalance.eq(1))
+ ) {
+ return; // noop
+ }
+ break;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+ public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ const allowance = await erc20Wrapper.getProxyAllowanceAsync(userAddress, assetData);
+ return allowance;
+ }
+ case AssetProxyId.ERC721: {
+ const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const erc721ProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+ const isProxyApprovedForAll = await assetWrapper.isProxyApprovedForAllAsync(
+ userAddress,
+ erc721ProxyData.tokenAddress,
+ );
+ if (isProxyApprovedForAll) {
+ return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
+ }
+
+ const isProxyApproved = await assetWrapper.isProxyApprovedAsync(
+ erc721ProxyData.tokenAddress,
+ erc721ProxyData.tokenId,
+ );
+ const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0);
+ return allowance;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+ public async setProxyAllowanceAsync(
+ userAddress: string,
+ assetData: string,
+ desiredAllowance: BigNumber,
+ ): Promise<void> {
+ const proxyId = assetProxyUtils.decodeAssetDataId(assetData);
+ switch (proxyId) {
+ case AssetProxyId.ERC20: {
+ const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
+ await erc20Wrapper.setAllowanceAsync(userAddress, assetData, desiredAllowance);
+ return;
+ }
+ case AssetProxyId.ERC721: {
+ if (
+ !desiredAllowance.eq(0) &&
+ !desiredAllowance.eq(1) &&
+ !desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
+ ) {
+ throw new Error(
+ `Allowance for ERC721 token can only be set to 0, 1 or 2^256-1. Got: ${desiredAllowance}`,
+ );
+ }
+ const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
+ const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData);
+
+ const doesTokenExist = await erc721Wrapper.doesTokenExistAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (!doesTokenExist) {
+ throw new Error(
+ `Cannot setProxyAllowance on non-existent token: ${assetProxyData.tokenAddress} ${
+ assetProxyData.tokenId
+ }`,
+ );
+ }
+ const isProxyApprovedForAll = await erc721Wrapper.isProxyApprovedForAllAsync(
+ userAddress,
+ assetProxyData.tokenAddress,
+ );
+ if (!isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ const isApproved = true;
+ await erc721Wrapper.approveProxyForAllAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ isApproved,
+ );
+ } else if (isProxyApprovedForAll && desiredAllowance.eq(0)) {
+ const isApproved = false;
+ await erc721Wrapper.approveProxyForAllAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ isApproved,
+ );
+ } else if (isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
+ return; // Noop
+ }
+
+ const isProxyApproved = await erc721Wrapper.isProxyApprovedAsync(
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ if (!isProxyApproved && desiredAllowance.eq(1)) {
+ await erc721Wrapper.approveProxyAsync(assetProxyData.tokenAddress, assetProxyData.tokenId);
+ } else if (isProxyApproved && desiredAllowance.eq(0)) {
+ // Remove approval
+ await erc721Wrapper.approveAsync(
+ constants.NULL_ADDRESS,
+ assetProxyData.tokenAddress,
+ assetProxyData.tokenId,
+ );
+ } else if (
+ (!isProxyApproved && desiredAllowance.eq(0)) ||
+ (isProxyApproved && desiredAllowance.eq(1))
+ ) {
+ return; // noop
+ }
+
+ break;
+ }
+ default:
+ throw errorUtils.spawnSwitchErr('proxyId', proxyId);
+ }
+ }
+}
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..5b725fbe3
--- /dev/null
+++ b/packages/contracts/src/utils/core_combinatorial_utils.ts
@@ -0,0 +1,801 @@
+import {
+ assetProxyUtils,
+ BalanceAndProxyAllowanceLazyStore,
+ ExchangeTransferSimulator,
+ orderHashUtils,
+ OrderStateUtils,
+ OrderValidationUtils,
+} from '@0xproject/order-utils';
+import { AssetProxyId, RevertReason, SignatureType, SignedOrder } from '@0xproject/types';
+import { BigNumber, errorUtils, logUtils } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as chai from 'chai';
+import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types';
+import * as _ from 'lodash';
+import 'make-promises-safe';
+
+import { ExchangeContract, FillContractEventArgs } from '../generated_contract_wrappers/exchange';
+import { artifacts } from '../utils/artifacts';
+import { expectRevertReasonOrAlwaysFailingTransactionAsync } 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 { OrderFactoryFromScenario } from '../utils/order_factory_from_scenario';
+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 {
+ AllowanceAmountScenario,
+ AssetDataScenario,
+ BalanceAmountScenario,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ FillScenario,
+ OrderAssetAmountScenario,
+ TakerAssetFillAmountScenario,
+ TakerScenario,
+ TraderStateScenario,
+} from '../utils/types';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+/**
+ * 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 accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const userAddresses = _.slice(accounts, 0, 5);
+ 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 OrderFactoryFromScenario(
+ 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: OrderFactoryFromScenario;
+ public ownerAddress: string;
+ public makerAddress: string;
+ public makerPrivateKey: Buffer;
+ public takerAddress: string;
+ public zrxAssetData: string;
+ public exchangeWrapper: ExchangeWrapper;
+ public assetWrapper: AssetWrapper;
+ public static generateFillOrderCombinations(): FillScenario[] {
+ const takerScenarios = [TakerScenario.Unspecified];
+ const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress];
+ const makerAssetAmountScenario = [OrderAssetAmountScenario.Large];
+ const takerAssetAmountScenario = [OrderAssetAmountScenario.Large];
+ const makerFeeScenario = [OrderAssetAmountScenario.Large];
+ const takerFeeScenario = [OrderAssetAmountScenario.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 takerAssetFillAmountScenario = [TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount];
+ const fillScenarioArrays = CoreCombinatorialUtils._getAllCombinations([
+ takerScenarios,
+ feeRecipientScenarios,
+ makerAssetAmountScenario,
+ takerAssetAmountScenario,
+ makerFeeScenario,
+ takerFeeScenario,
+ expirationTimeSecondsScenario,
+ makerAssetDataScenario,
+ takerAssetDataScenario,
+ takerAssetFillAmountScenario,
+ ]);
+
+ const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => {
+ const fillScenario: FillScenario = {
+ orderScenario: {
+ takerScenario: fillScenarioArray[0] as TakerScenario,
+ feeRecipientScenario: fillScenarioArray[1] as FeeRecipientAddressScenario,
+ makerAssetAmountScenario: fillScenarioArray[2] as OrderAssetAmountScenario,
+ takerAssetAmountScenario: fillScenarioArray[3] as OrderAssetAmountScenario,
+ makerFeeScenario: fillScenarioArray[4] as OrderAssetAmountScenario,
+ takerFeeScenario: fillScenarioArray[5] as OrderAssetAmountScenario,
+ expirationTimeSecondsScenario: fillScenarioArray[6] as ExpirationTimeSecondsScenario,
+ makerAssetDataScenario: fillScenarioArray[7] as AssetDataScenario,
+ takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario,
+ },
+ takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario,
+ makerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+ takerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+ };
+ return fillScenario;
+ });
+
+ return fillScenarios;
+ }
+ /**
+ * Recursive implementation of generating all combinations of the supplied
+ * string-containing arrays.
+ */
+ private static _getAllCombinations(arrays: string[][]): string[][] {
+ // Base case
+ if (arrays.length === 1) {
+ const remainingValues = _.map(arrays[0], val => {
+ return [val];
+ });
+ return remainingValues;
+ } else {
+ const result = [];
+ const restOfArrays = arrays.slice(1);
+ const allCombinationsOfRemaining = CoreCombinatorialUtils._getAllCombinations(restOfArrays); // recur with the rest of array
+ // tslint:disable:prefer-for-of
+ for (let i = 0; i < allCombinationsOfRemaining.length; i++) {
+ for (let j = 0; j < arrays[0].length; j++) {
+ result.push([arrays[0][j], ...allCombinationsOfRemaining[i]]);
+ }
+ }
+ // tslint:enable:prefer-for-of
+ return result;
+ }
+ }
+ constructor(
+ orderFactory: OrderFactoryFromScenario,
+ 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,
+ isVerbose: boolean = false,
+ ): Promise<void> {
+ // 1. Generate order
+ const order = this.orderFactory.generateOrder(fillScenario.orderScenario);
+
+ // 2. Sign order
+ const orderHashBuff = orderHashUtils.getOrderHashBuffer(order);
+ const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign);
+ const signedOrder = {
+ ...order,
+ signature: `0x${signature.toString('hex')}`,
+ };
+
+ const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper);
+ const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(
+ this.exchangeWrapper,
+ this.zrxAssetData,
+ );
+
+ // 3. Figure out fill amount
+ const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync(
+ signedOrder,
+ fillScenario.takerAssetFillAmountScenario,
+ balanceAndProxyAllowanceFetcher,
+ orderFilledCancelledFetcher,
+ );
+
+ // 4. Permutate the maker and taker balance/allowance scenarios
+ await this._modifyTraderStateAsync(
+ fillScenario.makerStateScenario,
+ fillScenario.takerStateScenario,
+ signedOrder,
+ takerAssetFillAmount,
+ );
+
+ // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected?
+ const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher);
+ const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
+ const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
+
+ let fillRevertReasonIfExists;
+ try {
+ await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ exchangeTransferSimulator,
+ provider,
+ signedOrder,
+ takerAssetFillAmount,
+ this.takerAddress,
+ this.zrxAssetData,
+ );
+ if (isVerbose) {
+ logUtils.log(`Expecting fillOrder to succeed.`);
+ }
+ } catch (err) {
+ fillRevertReasonIfExists = err.message;
+ if (isVerbose) {
+ logUtils.log(`Expecting fillOrder to fail with:`);
+ logUtils.log(err);
+ }
+ }
+
+ // 6. Fill the order
+ await this._fillOrderAndAssertOutcomeAsync(
+ signedOrder,
+ takerAssetFillAmount,
+ lazyStore,
+ fillRevertReasonIfExists,
+ );
+ }
+ private async _fillOrderAndAssertOutcomeAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ lazyStore: BalanceAndProxyAllowanceLazyStore,
+ fillRevertReasonIfExists: RevertReason | undefined,
+ ): Promise<void> {
+ if (!_.isUndefined(fillRevertReasonIfExists)) {
+ return expectRevertReasonOrAlwaysFailingTransactionAsync(
+ this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }),
+ fillRevertReasonIfExists,
+ );
+ }
+
+ 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',
+ );
+ }
+ private async _getTakerAssetFillAmountAsync(
+ signedOrder: SignedOrder,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario,
+ balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher,
+ orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher,
+ ): Promise<BigNumber> {
+ const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher);
+ const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync(
+ signedOrder,
+ this.takerAddress,
+ );
+
+ let takerAssetFillAmount;
+ 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 === AssetProxyId.ERC721 || makerAssetProxyId === AssetProxyId.ERC721;
+ 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);
+ }
+
+ return takerAssetFillAmount;
+ }
+ private async _modifyTraderStateAsync(
+ makerStateScenario: TraderStateScenario,
+ takerStateScenario: TraderStateScenario,
+ signedOrder: SignedOrder,
+ takerAssetFillAmount: BigNumber,
+ ): Promise<void> {
+ const makerAssetFillAmount = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+ switch (makerStateScenario.traderAssetBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (makerAssetFillAmount.eq(0)) {
+ throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`);
+ }
+ const tooLowBalance = makerAssetFillAmount.minus(1);
+ await this.assetWrapper.setBalanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ tooLowBalance,
+ );
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = makerAssetFillAmount;
+ await this.assetWrapper.setBalanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ exactBalance,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.traderAssetBalance',
+ makerStateScenario.traderAssetBalance,
+ );
+ }
+
+ const makerFee = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ switch (makerStateScenario.zrxFeeBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (makerFee.eq(0)) {
+ throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`);
+ }
+ const tooLowBalance = makerFee.minus(1);
+ await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance);
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = makerFee;
+ await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance);
+ }
+
+ switch (makerStateScenario.traderAssetAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = makerAssetFillAmount.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = makerAssetFillAmount;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ signedOrder.makerAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.traderAssetAllowance',
+ makerStateScenario.traderAssetAllowance,
+ );
+ }
+
+ switch (makerStateScenario.zrxFeeAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = makerFee.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = makerFee;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.makerAddress,
+ this.zrxAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'makerStateScenario.zrxFeeAllowance',
+ makerStateScenario.zrxFeeAllowance,
+ );
+ }
+
+ switch (takerStateScenario.traderAssetBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (takerAssetFillAmount.eq(0)) {
+ throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`);
+ }
+ const tooLowBalance = takerAssetFillAmount.minus(1);
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance);
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = takerAssetFillAmount;
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.traderAssetBalance',
+ takerStateScenario.traderAssetBalance,
+ );
+ }
+
+ const takerFee = orderUtils.getPartialAmount(
+ takerAssetFillAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ switch (takerStateScenario.zrxFeeBalance) {
+ case BalanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case BalanceAmountScenario.TooLow:
+ if (takerFee.eq(0)) {
+ throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`);
+ }
+ const tooLowBalance = takerFee.minus(1);
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance);
+ break;
+
+ case BalanceAmountScenario.Exact:
+ const exactBalance = takerFee;
+ await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance);
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance);
+ }
+
+ switch (takerStateScenario.traderAssetAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = takerAssetFillAmount.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = takerAssetFillAmount;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ this.takerAddress,
+ signedOrder.takerAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.traderAssetAllowance',
+ takerStateScenario.traderAssetAllowance,
+ );
+ }
+
+ switch (takerStateScenario.zrxFeeAllowance) {
+ case AllowanceAmountScenario.Higher:
+ break; // Noop since this is already the default
+
+ case AllowanceAmountScenario.TooLow:
+ const tooLowAllowance = takerFee.minus(1);
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ tooLowAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Exact:
+ const exactAllowance = takerFee;
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ exactAllowance,
+ );
+ break;
+
+ case AllowanceAmountScenario.Unlimited:
+ await this.assetWrapper.setProxyAllowanceAsync(
+ signedOrder.takerAddress,
+ this.zrxAssetData,
+ constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ );
+ break;
+
+ default:
+ throw errorUtils.spawnSwitchErr(
+ 'takerStateScenario.zrxFeeAllowance',
+ takerStateScenario.zrxFeeAllowance,
+ );
+ }
+ }
+} // tslint:disable:max-file-line-count
diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts
index 91c9d50b7..7f00e2061 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?: string;
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(
+ numberToDeploy: number,
+ decimals: BigNumber,
+ ): Promise<DummyERC20TokenContract[]> {
+ for (let i = 0; i < numberToDeploy; 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,
+ decimals,
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(): string {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as string;
+ }
public async setBalancesAndAllowancesAsync(): Promise<void> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
@@ -73,6 +83,36 @@ export class ERC20Wrapper {
}
}
}
+ public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress));
+ return balance;
+ }
+ public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
+ const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress));
+ return allowance;
+ }
+ public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(assetData);
+ const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, {
+ from: userAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
public async getBalancesAsync(): Promise<ERC20BalancesByOwner> {
this._validateDummyTokenContractsExistOrThrow();
const balancesByOwner: ERC20BalancesByOwner = {};
@@ -105,6 +145,15 @@ export class ERC20Wrapper {
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
return tokenAddresses;
}
+ private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract {
+ 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`);
+ }
+ return tokenContractIfExists;
+ }
private _validateDummyTokenContractsExistOrThrow(): void {
if (_.isUndefined(this._dummyTokenContracts)) {
throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"');
diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts
index b20d9acd2..309135ae4 100644
--- a/packages/contracts/src/utils/erc721_wrapper.ts
+++ b/packages/contracts/src/utils/erc721_wrapper.ts
@@ -19,6 +19,7 @@ export class ERC721Wrapper {
private _provider: Provider;
private _dummyTokenContracts: DummyERC721TokenContract[];
private _proxyContract?: ERC721ProxyContract;
+ private _proxyIdIfExists?: string;
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(): string {
+ this._validateProxyContractExistsOrThrow();
+ return this._proxyIdIfExists as string;
+ }
public async setBalancesAndAllowancesAsync(): Promise<void> {
this._validateDummyTokenContractsExistOrThrow();
this._validateProxyContractExistsOrThrow();
@@ -57,12 +63,7 @@ export class ERC721Wrapper {
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) {
const tokenId = generatePseudoRandomSalt();
- await this._web3Wrapper.awaitTransactionSuccessAsync(
- await dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, {
- from: this._contractOwnerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
+ await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) {
this._initialTokenIdsByOwner[tokenOwnerAddress] = {
[dummyTokenContract.address]: [],
@@ -72,19 +73,100 @@ export class ERC721Wrapper {
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
}
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
+
+ await this.approveProxyAsync(dummyTokenContract.address, tokenId);
}
- const shouldApprove = true;
- await this._web3Wrapper.awaitTransactionSuccessAsync(
- await dummyTokenContract.setApprovalForAll.sendTransactionAsync(
- (this._proxyContract as ERC721ProxyContract).address,
- shouldApprove,
- { from: tokenOwnerAddress },
- ),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
}
}
}
+ public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const doesExist = await tokenContract.exists.callAsync(tokenId);
+ return doesExist;
+ }
+ public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise<void> {
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ await this.approveAsync(proxyAddress, tokenAddress, tokenId);
+ }
+ public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, {
+ from: tokenOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.approve.sendTransactionAsync(to, tokenId, {
+ from: tokenOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async transferFromAsync(
+ tokenAddress: string,
+ tokenId: BigNumber,
+ currentOwner: string,
+ userAddress: string,
+ ): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, {
+ from: currentOwner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise<void> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ await this._web3Wrapper.awaitTransactionSuccessAsync(
+ await tokenContract.burn.sendTransactionAsync(owner, tokenId, {
+ from: this._contractOwnerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ }
+ public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise<string> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const owner = await tokenContract.ownerOf.callAsync(tokenId);
+ return owner;
+ }
+ public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId);
+ const isOwner = tokenOwner === userAddress;
+ return isOwner;
+ }
+ public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise<boolean> {
+ this._validateProxyContractExistsOrThrow();
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const operator = (this._proxyContract as ERC721ProxyContract).address;
+ const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator);
+ return didApproveAll;
+ }
+ public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
+ this._validateProxyContractExistsOrThrow();
+ const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
+ const approvedAddress = await tokenContract.getApproved.callAsync(tokenId);
+ const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
+ const isProxyAnApprovedOperator = approvedAddress === proxyAddress;
+ return isProxyAnApprovedOperator;
+ }
public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> {
this._validateDummyTokenContractsExistOrThrow();
this._validateBalancesAndAllowancesSetOrThrow();
@@ -127,6 +209,13 @@ export class ERC721Wrapper {
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
return tokenAddresses;
}
+ private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract {
+ const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
+ if (_.isUndefined(tokenContractIfExists)) {
+ throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
+ }
+ return tokenContractIfExists;
+ }
private _validateDummyTokenContractsExistOrThrow(): void {
if (_.isUndefined(this._dummyTokenContracts)) {
throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"');
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
index 06eb70644..ad68c8ff4 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/order_factory_from_scenario.ts b/packages/contracts/src/utils/order_factory_from_scenario.ts
new file mode 100644
index 000000000..b150e59f6
--- /dev/null
+++ b/packages/contracts/src/utils/order_factory_from_scenario.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,
+ OrderAssetAmountScenario,
+ OrderScenario,
+ TakerScenario,
+} from './types';
+
+const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10_000_000_000_000_000_000);
+const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5_000_000_000_000_000_000);
+const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000);
+const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000);
+const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000);
+const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000);
+const ONE_NFT_UNIT = new BigNumber(1);
+
+export class OrderFactoryFromScenario {
+ 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 OrderAssetAmountScenario.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 OrderAssetAmountScenario.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 OrderAssetAmountScenario.Zero:
+ makerAssetAmount = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerAssetAmountScenario);
+ }
+
+ switch (orderScenario.takerAssetAmountScenario) {
+ case OrderAssetAmountScenario.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 OrderAssetAmountScenario.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 OrderAssetAmountScenario.Zero:
+ takerAssetAmount = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerAssetAmountScenario);
+ }
+
+ switch (orderScenario.makerFeeScenario) {
+ case OrderAssetAmountScenario.Large:
+ makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Small:
+ makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Zero:
+ makerFee = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerFeeScenario);
+ }
+
+ switch (orderScenario.takerFeeScenario) {
+ case OrderAssetAmountScenario.Large:
+ takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Small:
+ takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
+ break;
+ case OrderAssetAmountScenario.Zero:
+ takerFee = new BigNumber(0);
+ break;
+ default:
+ throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', 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 = {
+ 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 5dfac64fc..b792bb90a 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -150,3 +150,80 @@ export interface MatchOrder {
leftSignature: string;
rightSignature: string;
}
+
+// Combinatorial testing types
+
+export enum FeeRecipientAddressScenario {
+ BurnAddress = 'BURN_ADDRESS',
+ EthUserAddress = 'ETH_USER_ADDRESS',
+}
+
+export enum OrderAssetAmountScenario {
+ Zero = 'ZERO',
+ Large = 'LARGE',
+ Small = 'SMALL',
+}
+
+export enum TakerScenario {
+ CorrectlySpecified = 'CORRECTLY_SPECIFIED',
+ IncorrectlySpecified = 'INCORRECTLY_SPECIFIED',
+ 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: OrderAssetAmountScenario;
+ takerAssetAmountScenario: OrderAssetAmountScenario;
+ makerFeeScenario: OrderAssetAmountScenario;
+ takerFeeScenario: OrderAssetAmountScenario;
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario;
+ makerAssetDataScenario: AssetDataScenario;
+ takerAssetDataScenario: AssetDataScenario;
+}
+
+export enum BalanceAmountScenario {
+ Exact = 'EXACT',
+ TooLow = 'TOO_LOW',
+ Higher = 'HIGHER',
+}
+
+export enum AllowanceAmountScenario {
+ Exact = 'EXACT',
+ TooLow = 'TOO_LOW',
+ Higher = 'HIGHER',
+ Unlimited = 'UNLIMITED',
+}
+
+export interface TraderStateScenario {
+ traderAssetBalance: BalanceAmountScenario;
+ traderAssetAllowance: AllowanceAmountScenario;
+ zrxFeeBalance: BalanceAmountScenario;
+ zrxFeeAllowance: AllowanceAmountScenario;
+}
+
+export interface FillScenario {
+ orderScenario: OrderScenario;
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario;
+ makerStateScenario: TraderStateScenario;
+ takerStateScenario: TraderStateScenario;
+}
diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts
index 595839519..39121d95c 100644
--- a/packages/contracts/test/asset_proxy/proxies.ts
+++ b/packages/contracts/test/asset_proxy/proxies.ts
@@ -5,6 +5,7 @@ import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import {
@@ -53,12 +54,17 @@ describe('Asset Transfer Proxies', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = accounts);
+ const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = _.slice(
+ accounts,
+ 0,
+ 5,
+ ));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 1;
+ [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
await web3Wrapper.awaitTransactionSuccessAsync(
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 36f6fa8d3..0c7381aac 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -6,16 +6,13 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
-import {
- CancelContractEventArgs,
- ExchangeContract,
- FillContractEventArgs,
-} from '../../src/generated_contract_wrappers/exchange';
+import { CancelContractEventArgs, ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
import { artifacts } from '../../src/utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
import { chaiSetup } from '../../src/utils/chai_setup';
@@ -66,12 +63,16 @@ describe('Exchange core', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
@@ -130,296 +131,6 @@ describe('Exchange core', () => {
erc20Balances = await erc20Wrapper.getBalancesAsync();
signedOrder = orderFactory.newSignedOrder();
});
- it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
- 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)),
- );
- });
-
- it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 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)),
- );
- });
-
- it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 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)),
- );
- });
-
- it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAddress,
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 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),
- );
- const expectedMakerAmountBoughtAfter = takerAssetFillAmount.add(takerAssetFilledAmountBefore);
- expect(makerAmountBoughtAfter).to.be.bignumber.equal(expectedMakerAmountBoughtAfter);
-
- 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)),
- );
- });
-
- it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => {
- const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
-
- const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
- takerAssetFillAmount: signedOrder.takerAssetAmount,
- });
- const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
- expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
- signedOrder.takerAssetAmount.minus(takerAssetFillAmount),
- );
- const newBalances = await erc20Wrapper.getBalancesAsync();
-
- expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
- );
- expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[makerAddress][defaultTakerAssetAddress].add(signedOrder.takerAssetAmount),
- );
- expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
- );
- expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[takerAddress][defaultTakerAssetAddress].minus(signedOrder.takerAssetAmount),
- );
- expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
- );
- expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
- );
- expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[feeRecipientAddress][zrxToken.address].add(
- signedOrder.makerFee.add(signedOrder.takerFee),
- ),
- );
- });
-
- it('should log 1 event with the correct arguments when order has a feeRecipient', async () => {
- const divisor = 2;
- const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
- takerAssetFillAmount: signedOrder.takerAssetAmount.div(divisor),
- });
- expect(res.logs).to.have.length(1);
-
- const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
- const logArgs = log.args;
- const expectedFilledMakerAssetAmount = signedOrder.makerAssetAmount.div(divisor);
- const expectedFilledTakerAssetAmount = signedOrder.takerAssetAmount.div(divisor);
- const expectedFeeMPaid = signedOrder.makerFee.div(divisor);
- const expectedFeeTPaid = signedOrder.takerFee.div(divisor);
-
- expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress);
- expect(takerAddress).to.be.equal(logArgs.takerAddress);
- expect(takerAddress).to.be.equal(logArgs.senderAddress);
- expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress);
- expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData);
- expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData);
- expect(expectedFilledMakerAssetAmount).to.be.bignumber.equal(logArgs.makerAssetFilledAmount);
- expect(expectedFilledTakerAssetAmount).to.be.bignumber.equal(logArgs.takerAssetFilledAmount);
- expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.makerFeePaid);
- expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.takerFeePaid);
- expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
- });
-
- it('should throw when taker is specified and order is claimed by other', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAddress: feeRecipientAddress,
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
- });
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.InvalidTaker,
- );
- });
it('should throw if signature is invalid', async () => {
signedOrder = orderFactory.newSignedOrder({
@@ -439,96 +150,6 @@ describe('Exchange core', () => {
);
});
- it('should throw if makerAssetAmount is 0', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: new BigNumber(0),
- });
-
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.OrderUnfillable,
- );
- });
-
- it('should throw if takerAssetAmount is 0', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAssetAmount: new BigNumber(0),
- });
-
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.OrderUnfillable,
- );
- });
-
- it('should throw if takerAssetFillAmount is 0', async () => {
- signedOrder = orderFactory.newSignedOrder();
-
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
- takerAssetFillAmount: new BigNumber(0),
- }),
- RevertReason.InvalidTakerAmount,
- );
- });
-
- it('should throw if maker erc20Balances are too low to fill order', async () => {
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
- });
-
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.TransferFailed,
- );
- });
-
- it('should throw if taker erc20Balances are too low to fill order', async () => {
- signedOrder = orderFactory.newSignedOrder({
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
- });
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.TransferFailed,
- );
- });
-
- it('should throw if maker allowances are too low to fill order', async () => {
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
- from: makerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.TransferFailed,
- );
- });
-
- it('should throw if taker allowances are too low to fill order', async () => {
- await web3Wrapper.awaitTransactionSuccessAsync(
- await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
- from: takerAddress,
- }),
- constants.AWAIT_TRANSACTION_MINED_MS,
- );
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.TransferFailed,
- );
- });
-
- it('should throw if an order is expired', async () => {
- signedOrder = orderFactory.newSignedOrder({
- expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
- });
- return expectRevertReasonOrAlwaysFailingTransactionAsync(
- exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
- RevertReason.OrderUnfillable,
- );
- });
-
it('should throw if no value is filled', async () => {
signedOrder = orderFactory.newSignedOrder();
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
@@ -725,32 +346,6 @@ describe('Exchange core', () => {
});
describe('Testing Exchange of ERC721 Tokens', () => {
- it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
- // Construct Exchange parameters
- const makerAssetId = erc721MakerAssetIds[0];
- const takerAssetId = erc721TakerAssetIds[1];
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: new BigNumber(1),
- takerAssetAmount: new BigNumber(1),
- makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId),
- });
- // Verify pre-conditions
- const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
- const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
- expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // Call Exchange
- const takerAssetFillAmount = signedOrder.takerAssetAmount;
- // tslint:disable-next-line:no-unused-variable
- const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- // Verify post-conditions
- const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
- });
-
it('should throw when maker does not own the token with id makerAssetId', async () => {
// Construct Exchange parameters
const makerAssetId = erc721TakerAssetIds[0];
@@ -885,86 +480,6 @@ describe('Exchange core', () => {
RevertReason.LengthGreaterThan131Required,
);
});
-
- it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => {
- // Construct Exchange parameters
- const makerAssetId = erc721MakerAssetIds[0];
- signedOrder = orderFactory.newSignedOrder({
- makerAssetAmount: new BigNumber(1),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress),
- });
- // Verify pre-conditions
- const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
- // Call Exchange
- erc20Balances = await erc20Wrapper.getBalancesAsync();
- const takerAssetFillAmount = signedOrder.takerAssetAmount;
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- // Verify ERC721 token was transferred from Maker to Taker
- const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
- // Verify ERC20 tokens were transferred from Taker to Maker & fees were paid correctly
- const newBalances = await erc20Wrapper.getBalancesAsync();
- expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
- );
- expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
- );
- expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
- );
- expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
- );
- expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[feeRecipientAddress][zrxToken.address].add(
- signedOrder.makerFee.add(signedOrder.takerFee),
- ),
- );
- });
-
- it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
- // Construct Exchange parameters
- const takerAssetId = erc721TakerAssetIds[0];
- signedOrder = orderFactory.newSignedOrder({
- takerAssetAmount: new BigNumber(1),
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
- takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId),
- makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress),
- });
- // Verify pre-conditions
- const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
- expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // Call Exchange
- erc20Balances = await erc20Wrapper.getBalancesAsync();
- const takerAssetFillAmount = signedOrder.takerAssetAmount;
- await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
- // Verify ERC721 token was transferred from Taker to Maker
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
- // Verify ERC20 tokens were transferred from Maker to Taker & fees were paid correctly
- const newBalances = await erc20Wrapper.getBalancesAsync();
- expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
- );
- expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
- erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
- );
- expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
- );
- expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
- );
- expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[feeRecipientAddress][zrxToken.address].add(
- signedOrder.makerFee.add(signedOrder.takerFee),
- ),
- );
- });
});
});
// tslint:disable:max-file-line-count
diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts
index 8adbee6a3..e35cca845 100644
--- a/packages/contracts/test/exchange/dispatcher.ts
+++ b/packages/contracts/test/exchange/dispatcher.ts
@@ -3,6 +3,7 @@ import { assetProxyUtils } from '@0xproject/order-utils';
import { AssetProxyId, RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
@@ -43,12 +44,13 @@ describe('AssetProxyDispatcher', () => {
before(async () => {
// Setup accounts & addresses
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = accounts);
+ const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = _.slice(accounts, 0, 4));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 1;
+ [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
diff --git a/packages/contracts/test/exchange/fill_order.ts b/packages/contracts/test/exchange/fill_order.ts
new file mode 100644
index 000000000..d65ab2f9a
--- /dev/null
+++ b/packages/contracts/test/exchange/fill_order.ts
@@ -0,0 +1,306 @@
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import * as _ from 'lodash';
+
+import { chaiSetup } from '../../src/utils/chai_setup';
+import { CoreCombinatorialUtils, coreCombinatorialUtilsFactoryAsync } from '../../src/utils/core_combinatorial_utils';
+import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+
+import {
+ AllowanceAmountScenario,
+ AssetDataScenario,
+ BalanceAmountScenario,
+ ExpirationTimeSecondsScenario,
+ FeeRecipientAddressScenario,
+ FillScenario,
+ OrderAssetAmountScenario,
+ TakerAssetFillAmountScenario,
+ TakerScenario,
+} from '../../src/utils/types';
+
+chaiSetup.configure();
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+const defaultFillScenario = {
+ orderScenario: {
+ takerScenario: TakerScenario.Unspecified,
+ feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress,
+ makerAssetAmountScenario: OrderAssetAmountScenario.Large,
+ takerAssetAmountScenario: OrderAssetAmountScenario.Large,
+ makerFeeScenario: OrderAssetAmountScenario.Large,
+ takerFeeScenario: OrderAssetAmountScenario.Large,
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture,
+ makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+ takerStateScenario: {
+ traderAssetBalance: BalanceAmountScenario.Higher,
+ traderAssetAllowance: AllowanceAmountScenario.Higher,
+ zrxFeeBalance: BalanceAmountScenario.Higher,
+ zrxFeeAllowance: AllowanceAmountScenario.Higher,
+ },
+};
+
+describe('FillOrder Tests', () => {
+ let coreCombinatorialUtils: CoreCombinatorialUtils;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ coreCombinatorialUtils = await coreCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults);
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('fillOrder', () => {
+ const test = (fillScenarios: FillScenario[]) => {
+ _.forEach(fillScenarios, fillScenario => {
+ const orderScenario = fillScenario.orderScenario;
+ const description = `Combinatorial OrderFill: ${orderScenario.feeRecipientScenario} ${
+ orderScenario.makerAssetAmountScenario
+ } ${orderScenario.takerAssetAmountScenario} ${orderScenario.makerFeeScenario} ${
+ orderScenario.takerFeeScenario
+ } ${orderScenario.expirationTimeSecondsScenario} ${orderScenario.makerAssetDataScenario} ${
+ orderScenario.takerAssetDataScenario
+ }`;
+ it(description, async () => {
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ });
+ };
+
+ const allFillScenarios = CoreCombinatorialUtils.generateFillOrderCombinations();
+ describe('Combinatorially generated fills orders', () => test(allFillScenarios));
+
+ it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerAssetAmountScenario: OrderAssetAmountScenario.Small,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetAmountScenario: OrderAssetAmountScenario.Small,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerScenario: TakerScenario.CorrectlySpecified,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ it('should throw when taker is specified and order is claimed by other', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerScenario: TakerScenario.IncorrectlySpecified,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if makerAssetAmount is 0', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetAmountScenario: OrderAssetAmountScenario.Zero,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if takerAssetAmount is 0', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ takerAssetAmountScenario: OrderAssetAmountScenario.Zero,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if takerAssetFillAmount is 0', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if an order is expired', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if maker erc20Balances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetBalance: BalanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if taker erc20Balances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetBalance: BalanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if maker allowances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should throw if taker allowances are too low to fill order', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ takerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.TooLow,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ });
+
+ describe('Testing Exchange of ERC721 Tokens', () => {
+ it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC721,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true);
+ });
+
+ it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ takerAssetDataScenario: AssetDataScenario.ERC721,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.Unlimited,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+
+ it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => {
+ const fillScenario = {
+ ...defaultFillScenario,
+ orderScenario: {
+ ...defaultFillScenario.orderScenario,
+ makerAssetDataScenario: AssetDataScenario.ERC721,
+ takerAssetDataScenario: AssetDataScenario.ERC721,
+ },
+ takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
+ makerStateScenario: {
+ ...defaultFillScenario.makerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.Unlimited,
+ },
+ takerStateScenario: {
+ ...defaultFillScenario.takerStateScenario,
+ traderAssetAllowance: AllowanceAmountScenario.Unlimited,
+ },
+ };
+ await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts
index bcf691471..e23ab9851 100644
--- a/packages/contracts/test/exchange/match_orders.ts
+++ b/packages/contracts/test/exchange/match_orders.ts
@@ -76,12 +76,16 @@ describe('matchOrders', () => {
takerAddress,
feeRecipientAddressLeft,
feeRecipientAddressRight,
- ] = accounts);
+ ] = _.slice(accounts, 0, 6));
// Create wrappers
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
// Deploy ERC20 token & ERC20 proxy
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
// Deploy ERC721 token and proxy
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
index b0c08e46e..c4de58bb9 100644
--- a/packages/contracts/test/exchange/transactions.ts
+++ b/packages/contracts/test/exchange/transactions.ts
@@ -3,6 +3,7 @@ import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-util
import { AssetProxyId, OrderWithoutExchangeAddress, RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
+import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
@@ -67,11 +68,19 @@ describe('Exchange transactions', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+ const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice(
+ accounts,
+ 0,
+ 5,
+ ));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts
index 9ee68f94d..1af8bde9d 100644
--- a/packages/contracts/test/exchange/wrapper.ts
+++ b/packages/contracts/test/exchange/wrapper.ts
@@ -60,12 +60,16 @@ describe('Exchange wrappers', () => {
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4));
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
- [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
@@ -370,7 +374,7 @@ describe('Exchange wrappers', () => {
// HACK(albrow): We need to hardcode the gas estimate here because
// the Geth gas estimator doesn't work with the way we use
// delegatecall and swallow errors.
- gas: 270000,
+ gas: 280000,
});
// Verify post-conditions
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
diff --git a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts
index ec398a11e..865ea4e43 100644
--- a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts
+++ b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts
@@ -3,5 +3,5 @@ import { BigNumber } from '@0xproject/utils';
export abstract class AbstractOrderFilledCancelledFetcher {
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
public abstract async isOrderCancelledAsync(orderHash: string): Promise<boolean>;
- public abstract getZRXTokenAddress(): string;
+ public abstract getZRXAssetData(): string;
}
diff --git a/packages/order-utils/src/exchange_transfer_simulator.ts b/packages/order-utils/src/exchange_transfer_simulator.ts
index 32d53d6a2..72ed85406 100644
--- a/packages/order-utils/src/exchange_transfer_simulator.ts
+++ b/packages/order-utils/src/exchange_transfer_simulator.ts
@@ -90,6 +90,9 @@ export class ExchangeTransferSimulator {
amountInBaseUnits: BigNumber,
): Promise<void> {
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, userAddress);
+ // 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)) {
this._store.setProxyAllowance(assetData, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index 514488e02..b4a7a6b67 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -16,6 +16,7 @@ export { generatePseudoRandomSalt } from './salt';
export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types';
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
+export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
export { RemainingFillableCalculator } from './remaining_fillable_calculator';
export { OrderStateUtils } from './order_state_utils';
export { assetProxyUtils } from './asset_proxy_utils';
diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts
index 40f235da7..dd45a1298 100644
--- a/packages/order-utils/src/order_state_utils.ts
+++ b/packages/order-utils/src/order_state_utils.ts
@@ -10,40 +10,81 @@ import { BigNumber } from '@0xproject/utils';
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 { orderHashUtils } from './order_hash';
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
+import { utils } from './utils';
+
+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 +98,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 +127,158 @@ 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 makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- makerAssetAddress,
- signedOrder.makerAddress,
+ 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 makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
+ const remainingFillableTakerAssetAmountGivenMakersStatus = signedOrder.makerAssetAmount.eq(0)
+ ? new BigNumber(0)
+ : utils.getPartialAmount(
+ orderRelevantMakerState.remainingFillableAssetAmount,
+ signedOrder.makerAssetAmount,
+ signedOrder.takerAssetAmount,
+ );
+
+ // 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 makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
+ const takerAssetAllowanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ signedOrder.takerAssetData,
+ takerAddress,
);
+ 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 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;
}
}
diff --git a/packages/order-utils/src/order_validation_utils.ts b/packages/order-utils/src/order_validation_utils.ts
index 3a6704f26..54428f77d 100644
--- a/packages/order-utils/src/order_validation_utils.ts
+++ b/packages/order-utils/src/order_validation_utils.ts
@@ -1,24 +1,19 @@
-import { ExchangeContractErrs, Order, SignedOrder } from '@0xproject/types';
+import { RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { OrderError, TradeSide, TransferType } from './types';
+import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
import { constants } from './constants';
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
-import { ExchangeContract } from './generated_contract_wrappers/exchange';
import { orderHashUtils } from './order_hash';
import { isValidSignatureAsync } from './signature_utils';
import { utils } from './utils';
export class OrderValidationUtils {
- private _exchangeContract: ExchangeContract;
- // TODO: Write some tests for the function
- // const numerator = new BigNumber(20);
- // const denominator = new BigNumber(999);
- // const target = new BigNumber(50);
- // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
+ private _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
public static isRoundingError(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean {
// Solidity's mulmod() in JS
// Source: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions
@@ -36,136 +31,120 @@ export class OrderValidationUtils {
const isError = errPercentageTimes1000000.gt(1000);
return isError;
}
- public static validateCancelOrderThrowIfInvalid(
- order: Order,
- cancelTakerTokenAmount: BigNumber,
- filledTakerTokenAmount: BigNumber,
- ): void {
- if (cancelTakerTokenAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
- }
- if (order.takerAssetAmount.eq(filledTakerTokenAmount)) {
- throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
- }
- const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
- if (order.expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderCancelExpired);
- }
- }
public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
- fillTakerTokenAmount: BigNumber,
+ fillTakerAssetAmount: BigNumber,
senderAddress: string,
- zrxTokenAddress: string,
+ zrxAssetData: string,
): Promise<void> {
- const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
- fillTakerTokenAmount,
- signedOrder.takerAssetAmount,
- signedOrder.makerAssetAmount,
- );
- await exchangeTradeEmulator.transferFromAsync(
- signedOrder.makerAssetData,
- signedOrder.makerAddress,
- senderAddress,
- fillMakerTokenAmount,
- TradeSide.Maker,
- TransferType.Trade,
- );
- await exchangeTradeEmulator.transferFromAsync(
- signedOrder.takerAssetData,
- senderAddress,
- signedOrder.makerAddress,
- fillTakerTokenAmount,
- TradeSide.Taker,
- TransferType.Trade,
- );
- const makerFeeAmount = OrderValidationUtils._getPartialAmount(
- fillTakerTokenAmount,
- signedOrder.takerAssetAmount,
- signedOrder.makerFee,
- );
- await exchangeTradeEmulator.transferFromAsync(
- zrxTokenAddress,
- signedOrder.makerAddress,
- signedOrder.feeRecipientAddress,
- makerFeeAmount,
- TradeSide.Maker,
- TransferType.Fee,
- );
- const takerFeeAmount = OrderValidationUtils._getPartialAmount(
- fillTakerTokenAmount,
- signedOrder.takerAssetAmount,
- signedOrder.takerFee,
- );
- await exchangeTradeEmulator.transferFromAsync(
- zrxTokenAddress,
- senderAddress,
- signedOrder.feeRecipientAddress,
- takerFeeAmount,
- TradeSide.Taker,
- TransferType.Fee,
- );
+ try {
+ const fillMakerTokenAmount = utils.getPartialAmount(
+ fillTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerAssetAmount,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.makerAssetData,
+ signedOrder.makerAddress,
+ senderAddress,
+ fillMakerTokenAmount,
+ TradeSide.Maker,
+ TransferType.Trade,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ signedOrder.takerAssetData,
+ senderAddress,
+ signedOrder.makerAddress,
+ fillTakerAssetAmount,
+ TradeSide.Taker,
+ TransferType.Trade,
+ );
+ const makerFeeAmount = utils.getPartialAmount(
+ fillTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.makerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxAssetData,
+ signedOrder.makerAddress,
+ signedOrder.feeRecipientAddress,
+ makerFeeAmount,
+ TradeSide.Maker,
+ TransferType.Fee,
+ );
+ const takerFeeAmount = utils.getPartialAmount(
+ fillTakerAssetAmount,
+ signedOrder.takerAssetAmount,
+ signedOrder.takerFee,
+ );
+ await exchangeTradeEmulator.transferFromAsync(
+ zrxAssetData,
+ senderAddress,
+ signedOrder.feeRecipientAddress,
+ takerFeeAmount,
+ TradeSide.Taker,
+ TransferType.Fee,
+ );
+ } catch (err) {
+ throw new Error(RevertReason.TransferFailed);
+ }
}
private static _validateRemainingFillAmountNotZeroOrThrow(
takerAssetAmount: BigNumber,
filledTakerTokenAmount: BigNumber,
): void {
if (takerAssetAmount.eq(filledTakerTokenAmount)) {
- throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ throw new Error(RevertReason.OrderUnfillable);
}
}
private static _validateOrderNotExpiredOrThrow(expirationTimeSeconds: BigNumber): void {
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderFillExpired);
+ throw new Error(RevertReason.OrderUnfillable);
}
}
- private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
- const fillMakerTokenAmount = numerator
- .mul(target)
- .div(denominator)
- .round(0);
- return fillMakerTokenAmount;
- }
- constructor(exchangeContract: ExchangeContract) {
- this._exchangeContract = exchangeContract;
+ constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) {
+ this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
}
public async validateOrderFillableOrThrowAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
- zrxTokenAddress: string,
+ zrxAssetData: string,
expectedFillTakerTokenAmount?: BigNumber,
): Promise<void> {
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
- const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
+ const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerAssetAmount,
filledTakerTokenAmount,
);
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
- let fillTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
+ let fillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
- fillTakerTokenAmount = expectedFillTakerTokenAmount;
+ fillTakerAssetAmount = expectedFillTakerTokenAmount;
}
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
- fillTakerTokenAmount,
+ fillTakerAssetAmount,
signedOrder.takerAddress,
- zrxTokenAddress,
+ zrxAssetData,
);
}
public async validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
provider: Provider,
signedOrder: SignedOrder,
- fillTakerTokenAmount: BigNumber,
+ fillTakerAssetAmount: BigNumber,
takerAddress: string,
- zrxTokenAddress: string,
+ zrxAssetData: string,
): Promise<BigNumber> {
- if (fillTakerTokenAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderFillAmountZero);
+ if (fillTakerAssetAmount.eq(0)) {
+ throw new Error(RevertReason.InvalidTakerAmount);
+ }
+ if (signedOrder.makerAssetAmount.eq(0) || signedOrder.takerAssetAmount.eq(0)) {
+ throw new Error(RevertReason.OrderUnfillable);
}
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
const isValid = await isValidSignatureAsync(
@@ -177,34 +156,34 @@ export class OrderValidationUtils {
if (!isValid) {
throw new Error(OrderError.InvalidSignature);
}
- const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
+ const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerAssetAmount,
filledTakerTokenAmount,
);
if (signedOrder.takerAddress !== constants.NULL_ADDRESS && signedOrder.takerAddress !== takerAddress) {
- throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
+ throw new Error(RevertReason.InvalidTaker);
}
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
const remainingTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
- const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
+ const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerAssetAmount)
? remainingTakerTokenAmount
- : fillTakerTokenAmount;
+ : fillTakerAssetAmount;
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
desiredFillTakerTokenAmount,
takerAddress,
- zrxTokenAddress,
+ zrxAssetData,
);
const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingError(
- filledTakerTokenAmount,
+ desiredFillTakerTokenAmount,
signedOrder.takerAssetAmount,
signedOrder.makerAssetAmount,
);
if (wouldRoundingErrorOccur) {
- throw new Error(ExchangeContractErrs.OrderFillRoundingError);
+ throw new Error(RevertReason.RoundingError);
}
return filledTakerTokenAmount;
}
@@ -212,20 +191,20 @@ export class OrderValidationUtils {
exchangeTradeEmulator: ExchangeTransferSimulator,
provider: Provider,
signedOrder: SignedOrder,
- fillTakerTokenAmount: BigNumber,
+ fillTakerAssetAmount: BigNumber,
takerAddress: string,
- zrxTokenAddress: string,
+ zrxAssetData: string,
): Promise<void> {
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator,
provider,
signedOrder,
- fillTakerTokenAmount,
+ fillTakerAssetAmount,
takerAddress,
- zrxTokenAddress,
+ zrxAssetData,
);
- if (filledTakerTokenAmount !== fillTakerTokenAmount) {
- throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
+ if (filledTakerTokenAmount !== fillTakerAssetAmount) {
+ throw new Error(RevertReason.OrderUnfillable);
}
}
}
diff --git a/packages/order-utils/src/remaining_fillable_calculator.ts b/packages/order-utils/src/remaining_fillable_calculator.ts
index bc146e931..29e19e5ab 100644
--- a/packages/order-utils/src/remaining_fillable_calculator.ts
+++ b/packages/order-utils/src/remaining_fillable_calculator.ts
@@ -23,9 +23,9 @@ export class RemainingFillableCalculator {
this._transferrableAssetAmount = transferrableAssetAmount;
this._transferrableFeeAmount = transferrableFeeAmount;
this._remainingOrderAssetAmount = remainingOrderAssetAmount;
- this._remainingOrderFeeAmount = remainingOrderAssetAmount
- .times(this._orderFee)
- .dividedToIntegerBy(this._orderAssetAmount);
+ this._remainingOrderFeeAmount = orderAssetAmount.eq(0)
+ ? new BigNumber(0)
+ : remainingOrderAssetAmount.times(orderFee).dividedToIntegerBy(orderAssetAmount);
}
public computeRemainingFillable(): BigNumber {
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
diff --git a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
index 08d50b924..e7352119d 100644
--- a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
+++ b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
@@ -19,8 +19,8 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx
[userAddress: string]: BigNumber;
};
};
- constructor(token: AbstractBalanceAndProxyAllowanceFetcher) {
- this._balanceAndProxyAllowanceFetcher = token;
+ constructor(balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher) {
+ this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
this._balance = {};
this._proxyAllowance = {};
}
diff --git a/packages/order-utils/src/utils.ts b/packages/order-utils/src/utils.ts
index 6149316f6..7aaaf0609 100644
--- a/packages/order-utils/src/utils.ts
+++ b/packages/order-utils/src/utils.ts
@@ -12,4 +12,11 @@ export const utils = {
const milisecondsInSecond = 1000;
return new BigNumber(Date.now() / milisecondsInSecond).round();
},
+ getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
+ const fillMakerTokenAmount = numerator
+ .mul(target)
+ .div(denominator)
+ .round(0);
+ return fillMakerTokenAmount;
+ },
};
diff --git a/packages/order-utils/test/exchange_transfer_simulator_test.ts b/packages/order-utils/test/exchange_transfer_simulator_test.ts
index 52385f611..bed04d879 100644
--- a/packages/order-utils/test/exchange_transfer_simulator_test.ts
+++ b/packages/order-utils/test/exchange_transfer_simulator_test.ts
@@ -4,6 +4,7 @@ import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { artifacts } from '../src/artifacts';
+import { assetProxyUtils } from '../src/asset_proxy_utils';
import { constants } from '../src/constants';
import { ExchangeTransferSimulator } from '../src/exchange_transfer_simulator';
import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_e_r_c20_token';
@@ -27,7 +28,7 @@ describe('ExchangeTransferSimulator', async () => {
let coinbase: string;
let sender: string;
let recipient: string;
- let exampleTokenAddress: string;
+ let exampleAssetData: string;
let exchangeTransferSimulator: ExchangeTransferSimulator;
let txHash: string;
let erc20ProxyAddress: string;
@@ -65,7 +66,7 @@ describe('ExchangeTransferSimulator', async () => {
totalSupply,
);
- exampleTokenAddress = dummyERC20Token.address;
+ exampleAssetData = assetProxyUtils.encodeERC20AssetData(dummyERC20Token.address);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -91,7 +92,7 @@ describe('ExchangeTransferSimulator', async () => {
it("throws if the user doesn't have enough allowance", async () => {
return expect(
exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -107,7 +108,7 @@ describe('ExchangeTransferSimulator', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
return expect(
exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -128,7 +129,7 @@ describe('ExchangeTransferSimulator', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -136,9 +137,9 @@ describe('ExchangeTransferSimulator', async () => {
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
- const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
- const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
- const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
+ const senderBalance = await store.getBalanceAsync(exampleAssetData, sender);
+ const recipientBalance = await store.getBalanceAsync(exampleAssetData, recipient);
+ const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleAssetData, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(0);
@@ -157,7 +158,7 @@ describe('ExchangeTransferSimulator', async () => {
);
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
- exampleTokenAddress,
+ exampleAssetData,
sender,
recipient,
transferAmount,
@@ -165,9 +166,9 @@ describe('ExchangeTransferSimulator', async () => {
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
- const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
- const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
- const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
+ const senderBalance = await store.getBalanceAsync(exampleAssetData, sender);
+ const recipientBalance = await store.getBalanceAsync(exampleAssetData, recipient);
+ const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleAssetData, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
diff --git a/packages/order-watcher/test/global_hooks.ts b/packages/order-watcher/test/global_hooks.ts
index 9d6903af5..30b0cd697 100644
--- a/packages/order-watcher/test/global_hooks.ts
+++ b/packages/order-watcher/test/global_hooks.ts
@@ -6,7 +6,7 @@ import { provider } from './utils/web3_wrapper';
before('migrate contracts', async function(): Promise<void> {
// HACK: Since the migrations take longer then our global mocha timeout limit
// we manually increase it for this before hook.
- const mochaTestTimeoutMs = 20000;
+ const mochaTestTimeoutMs = 25000;
this.timeout(mochaTestTimeoutMs);
const txDefaults = {
gas: devConstants.GAS_LIMIT,