From 61243b418e4d962cd8d8a1d7a49f04510b3c1c7f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 13 Jun 2018 16:09:04 +0200 Subject: Implement initial set of orderFill combinatorial tests --- .../src/utils/core_combinatorial_utils.ts | 445 +++++++++++++++++++++ packages/contracts/src/utils/exchange_wrapper.ts | 4 + packages/contracts/src/utils/new_order_factory.ts | 130 ++---- packages/contracts/src/utils/order_info_utils.ts | 44 -- packages/contracts/src/utils/order_utils.ts | 7 + ...le_asset_balance_and_proxy_allowance_fetcher.ts | 19 + .../simple_erc20_balance_and_allowance_fetcher.ts | 20 - .../src/utils/simple_filled_cancelled_fetcher.ts | 32 -- .../utils/simple_order_filled_cancelled_fetcher.ts | 24 ++ packages/contracts/src/utils/types.ts | 15 +- 10 files changed, 553 insertions(+), 187 deletions(-) create mode 100644 packages/contracts/src/utils/core_combinatorial_utils.ts delete mode 100644 packages/contracts/src/utils/order_info_utils.ts create mode 100644 packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts delete mode 100644 packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts delete mode 100644 packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts create mode 100644 packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts (limited to 'packages/contracts/src') diff --git a/packages/contracts/src/utils/core_combinatorial_utils.ts b/packages/contracts/src/utils/core_combinatorial_utils.ts new file mode 100644 index 000000000..6fbd8304e --- /dev/null +++ b/packages/contracts/src/utils/core_combinatorial_utils.ts @@ -0,0 +1,445 @@ +import { + assetProxyUtils, + BalanceAndProxyAllowanceLazyStore, + ExchangeTransferSimulator, + orderHashUtils, + OrderStateUtils, + OrderValidationUtils, +} from '@0xproject/order-utils'; +import { AssetProxyId, Order, SignatureType, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import { BlockParamLiteral, LogWithDecodedArgs, Provider, TxData } from 'ethereum-types'; +// import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; +import 'make-promises-safe'; + +import { ExchangeContract, FillContractEventArgs } from '../generated_contract_wrappers/exchange'; +import { artifacts } from '../utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../utils/assertions'; +import { AssetWrapper } from '../utils/asset_wrapper'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { NewOrderFactory } from '../utils/new_order_factory'; +import { orderUtils } from '../utils/order_utils'; +import { signingUtils } from '../utils/signing_utils'; +import { SimpleAssetBalanceAndProxyAllowanceFetcher } from '../utils/simple_asset_balance_and_proxy_allowance_fetcher'; +import { SimpleOrderFilledCancelledFetcher } from '../utils/simple_order_filled_cancelled_fetcher'; +import { + AssetDataScenario, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + OrderAmountScenario, + OrderScenario, +} from '../utils/types'; + +chaiSetup.configure(); +const expect = chai.expect; + +const ERC721_PROXY_ID = 2; + +/** + * Instantiates a new instance of CoreCombinatorialUtils. Since this method has some + * required async setup, a factory method is required. + * @param web3Wrapper Web3Wrapper instance + * @param txDefaults Default Ethereum tx options + * @return CoreCombinatorialUtils instance + */ +export async function coreCombinatorialUtilsFactoryAsync( + web3Wrapper: Web3Wrapper, + txDefaults: Partial, +): Promise { + const userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + const [ownerAddress, makerAddress, takerAddress] = userAddresses; + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; + + const provider = web3Wrapper.getProvider(); + const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress); + const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress); + + const erc20EighteenDecimalTokenCount = 3; + const eighteenDecimals = new BigNumber(18); + const [ + erc20EighteenDecimalTokenA, + erc20EighteenDecimalTokenB, + zrxToken, + ] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals); + const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + + const erc20FiveDecimalTokenCount = 2; + const fiveDecimals = new BigNumber(18); + const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( + erc20FiveDecimalTokenCount, + fiveDecimals, + ); + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + const erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + + const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]); + + const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider); + await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, ownerAddress); + await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, ownerAddress); + + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { + from: ownerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { + from: ownerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const orderFactory = new NewOrderFactory( + userAddresses, + zrxToken.address, + [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address], + [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address], + erc721Token, + erc721Balances, + exchangeContract.address, + ); + + const coreCombinatorialUtils = new CoreCombinatorialUtils( + orderFactory, + ownerAddress, + makerAddress, + makerPrivateKey, + takerAddress, + zrxAssetData, + exchangeWrapper, + assetWrapper, + ); + return coreCombinatorialUtils; +} + +export class CoreCombinatorialUtils { + public orderFactory: NewOrderFactory; + public ownerAddress: string; + public makerAddress: string; + public makerPrivateKey: Buffer; + public takerAddress: string; + public zrxAssetData: string; + public exchangeWrapper: ExchangeWrapper; + public assetWrapper: AssetWrapper; + public static generateOrderCombinations(): OrderScenario[] { + const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress]; + const makerAssetAmountScenario = [OrderAmountScenario.NonZero]; + const takerAssetAmountScenario = [OrderAmountScenario.NonZero]; + const makerFeeScenario = [OrderAmountScenario.NonZero]; + const takerFeeScenario = [OrderAmountScenario.NonZero]; + const expirationTimeSecondsScenario = [ExpirationTimeSecondsScenario.InFuture]; + const makerAssetDataScenario = [ + AssetDataScenario.ERC20FiveDecimals, + AssetDataScenario.ERC20NonZRXEighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ZRXFeeToken, + ]; + const takerAssetDataScenario = [ + AssetDataScenario.ERC20FiveDecimals, + AssetDataScenario.ERC20NonZRXEighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ZRXFeeToken, + ]; + const orderScenarioArrays = CoreCombinatorialUtils._allPossibleCases([ + feeRecipientScenarios, + makerAssetAmountScenario, + takerAssetAmountScenario, + makerFeeScenario, + takerFeeScenario, + expirationTimeSecondsScenario, + makerAssetDataScenario, + takerAssetDataScenario, + ]); + + const orderScenarios = _.map(orderScenarioArrays, orderScenarioArray => { + const orderScenario: OrderScenario = { + feeRecipientScenario: orderScenarioArray[0] as FeeRecipientAddressScenario, + makerAssetAmountScenario: orderScenarioArray[1] as OrderAmountScenario, + takerAssetAmountScenario: orderScenarioArray[2] as OrderAmountScenario, + makerFeeScenario: orderScenarioArray[3] as OrderAmountScenario, + takerFeeScenario: orderScenarioArray[4] as OrderAmountScenario, + expirationTimeSecondsScenario: orderScenarioArray[5] as ExpirationTimeSecondsScenario, + makerAssetDataScenario: orderScenarioArray[6] as AssetDataScenario, + takerAssetDataScenario: orderScenarioArray[7] as AssetDataScenario, + }; + return orderScenario; + }); + + return orderScenarios; + } + private static _allPossibleCases(arrays: string[][]): string[][] { + if (arrays.length === 1) { + const remainingVals = _.map(arrays[0], val => { + return [val]; + }); + return remainingVals; + } else { + const result = []; + const allCasesOfRest = CoreCombinatorialUtils._allPossibleCases(arrays.slice(1)); // recur with the rest of array + // tslint:disable:prefer-for-of + for (let i = 0; i < allCasesOfRest.length; i++) { + for (let j = 0; j < arrays[0].length; j++) { + result.push([arrays[0][j], ...allCasesOfRest[i]]); + } + } + // tslint:enable:prefer-for-of + return result; + } + } + constructor( + orderFactory: NewOrderFactory, + ownerAddress: string, + makerAddress: string, + makerPrivateKey: Buffer, + takerAddress: string, + zrxAssetData: string, + exchangeWrapper: ExchangeWrapper, + assetWrapper: AssetWrapper, + ) { + this.orderFactory = orderFactory; + this.ownerAddress = ownerAddress; + this.makerAddress = makerAddress; + this.makerPrivateKey = makerPrivateKey; + this.takerAddress = takerAddress; + this.zrxAssetData = zrxAssetData; + this.exchangeWrapper = exchangeWrapper; + this.assetWrapper = assetWrapper; + } + public async testFillOrderScenarioAsync(order: Order, provider: Provider): Promise { + // 2. Sign order + const orderHashBuff = orderHashUtils.getOrderHashBuff(order); + const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign); + const signedOrder = { + ...order, + signature: `0x${signature.toString('hex')}`, + }; + + // 3. Permutate the maker and taker balance/allowance scenarios + + // 4. Figure out fill amount OR error + const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper); + const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher( + this.exchangeWrapper, + this.zrxAssetData, + ); + const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher); + + const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync( + signedOrder, + this.takerAddress, + ); + + // If order is fillable, decide how much to fill + // TODO: Make this configurable + const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData); + const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.makerAssetData); + const isEitherAssetERC721 = takerAssetProxyId === ERC721_PROXY_ID || makerAssetProxyId === ERC721_PROXY_ID; + const takerAssetFillAmount = isEitherAssetERC721 + ? fillableTakerAssetAmount + : fillableTakerAssetAmount.div(2).floor(); + + // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts exp? + const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher); + const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher); + const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore); + + let isFillFailureExpected = false; + try { + await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTransferSimulator, + provider, + signedOrder, + takerAssetFillAmount, + this.takerAddress, + this.zrxAssetData, + ); + } catch (err) { + isFillFailureExpected = true; + } + + await this._fillOrderAndAssertOutcomeAsync( + signedOrder, + takerAssetFillAmount, + lazyStore, + isFillFailureExpected, + provider, + ); + } + private async _fillOrderAndAssertOutcomeAsync( + signedOrder: SignedOrder, + takerAssetFillAmount: BigNumber, + lazyStore: BalanceAndProxyAllowanceLazyStore, + isFillFailureExpected: boolean, + provider: Provider, + ): Promise { + if (isFillFailureExpected) { + return expectRevertOrAlwaysFailingTransactionAsync( + this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }), + ); + } + + const makerAddress = signedOrder.makerAddress; + const makerAssetData = signedOrder.makerAssetData; + const takerAssetData = signedOrder.takerAssetData; + const feeRecipient = signedOrder.feeRecipientAddress; + + const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress); + const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress); + const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress); + const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress); + const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress); + const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress); + const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress); + const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress); + const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress); + const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync( + this.zrxAssetData, + this.takerAddress, + ); + const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient); + + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const initialFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + const expFilledTakerAmount = initialFilledTakerAmount.add(takerAssetFillAmount); + + const expFilledMakerAmount = orderUtils.getPartialAmount( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.makerAssetAmount, + ); + + // - Let's fill the order! + const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { + takerAssetFillAmount, + }); + + const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount'); + + expect(txReceipt.logs.length).to.be.equal(1, 'logs length'); + // tslint:disable-next-line:no-unnecessary-type-assertion + const log = txReceipt.logs[0] as LogWithDecodedArgs; + expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress'); + expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress'); + expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress'); + expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal( + expFilledMakerAmount, + 'log.args.makerAssetFilledAmount', + ); + expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal( + takerAssetFillAmount, + 'log.args.takerAssetFilledAmount', + ); + const expMakerFeePaid = orderUtils.getPartialAmount( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.makerFee, + ); + expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid'); + const expTakerFeePaid = orderUtils.getPartialAmount( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.takerFee, + ); + expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid'); + expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash'); + expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData'); + expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData'); + + const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData); + expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal( + expMakerAssetBalanceOfMaker, + 'makerAssetBalanceOfMaker', + ); + + const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( + makerAddress, + makerAssetData, + ); + expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal( + expMakerAssetAllowanceOfMaker, + 'makerAssetAllowanceOfMaker', + ); + + const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData); + expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal( + expTakerAssetBalanceOfMaker, + 'takerAssetBalanceOfMaker', + ); + + const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData); + expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker'); + + const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( + makerAddress, + this.zrxAssetData, + ); + expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal( + expZRXAssetAllowanceOfMaker, + 'ZRXAssetAllowanceOfMaker', + ); + + const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData); + expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal( + expTakerAssetBalanceOfTaker, + 'TakerAssetBalanceOfTaker', + ); + + const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( + this.takerAddress, + takerAssetData, + ); + + expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal( + expTakerAssetAllowanceOfTaker, + 'TakerAssetAllowanceOfTaker', + ); + + const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData); + expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal( + expMakerAssetBalanceOfTaker, + 'MakerAssetBalanceOfTaker', + ); + + const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData); + expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker'); + + const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( + this.takerAddress, + this.zrxAssetData, + ); + expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal( + expZRXAssetAllowanceOfTaker, + 'ZRXAssetAllowanceOfTaker', + ); + + const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync( + feeRecipient, + this.zrxAssetData, + ); + expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal( + expZRXAssetBalanceOfFeeRecipient, + 'ZRXAssetBalanceOfFeeRecipient', + ); + } +} diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 1ecabcf64..e74b91fe2 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -227,6 +227,10 @@ export class ExchangeWrapper { const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex)); return filledAmount; } + public async isCancelledAsync(orderHashHex: string): Promise { + const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); + return isCancelled; + } public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; return orderInfo; diff --git a/packages/contracts/src/utils/new_order_factory.ts b/packages/contracts/src/utils/new_order_factory.ts index a4ded4230..715e38d6d 100644 --- a/packages/contracts/src/utils/new_order_factory.ts +++ b/packages/contracts/src/utils/new_order_factory.ts @@ -2,7 +2,7 @@ import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-util import { Order } from '@0xproject/types'; import { BigNumber, errorUtils } from '@0xproject/utils'; -import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token'; +import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token'; import { constants } from './constants'; import { @@ -11,20 +11,13 @@ import { ExpirationTimeSecondsScenario, FeeRecipientAddressScenario, OrderAmountScenario, + OrderScenario, } from './types'; const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10000000000000000000); const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100000000000000000); const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1000000); const ONE_NFT_UNIT = new BigNumber(1); -const TEN_MINUTES_MS = 1000 * 60 * 10; - -/* - * TODO: - * - Write function that given an order, fillAmount, retrieves orderRelevantState and maps it to expected test outcome. - * - Write function that generates order permutations. - * - Write functions for other steps that must be permutated - */ export class NewOrderFactory { private _userAddresses: string[]; @@ -51,16 +44,7 @@ export class NewOrderFactory { this._erc721Balances = erc721Balances; this._exchangeAddress = exchangeAddress; } - public generateOrder( - feeRecipientScenario: FeeRecipientAddressScenario, - makerAssetAmountScenario: OrderAmountScenario, - takerAssetAmountScenario: OrderAmountScenario, - makerFeeScenario: OrderAmountScenario, - takerFeeScenario: OrderAmountScenario, - expirationTimeSecondsScenario: ExpirationTimeSecondsScenario, - makerAssetDataScenario: AssetDataScenario, - takerAssetDataScenario: AssetDataScenario, - ): Order { + public generateOrder(orderScenario: OrderScenario): Order { const makerAddress = this._userAddresses[1]; const takerAddress = this._userAddresses[2]; const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address]; @@ -74,7 +58,7 @@ export class NewOrderFactory { let makerAssetData; let takerAssetData; - switch (feeRecipientScenario) { + switch (orderScenario.feeRecipientScenario) { case FeeRecipientAddressScenario.BurnAddress: feeRecipientAddress = constants.NULL_ADDRESS; break; @@ -82,135 +66,102 @@ export class NewOrderFactory { feeRecipientAddress = this._userAddresses[4]; break; default: - throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', feeRecipientScenario); + throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario); } - const invalidAssetProxyIdHex = '0A'; - switch (makerAssetDataScenario) { + switch (orderScenario.makerAssetDataScenario) { case AssetDataScenario.ZRXFeeToken: - makerAssetData = assetProxyUtils.encodeERC20ProxyData(this._zrxAddress); + makerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress); break; case AssetDataScenario.ERC20NonZRXEighteenDecimals: - makerAssetData = assetProxyUtils.encodeERC20ProxyData( + makerAssetData = assetProxyUtils.encodeERC20AssetData( this._nonZrxERC20EighteenDecimalTokenAddresses[0], ); break; case AssetDataScenario.ERC20FiveDecimals: - makerAssetData = assetProxyUtils.encodeERC20ProxyData(this._erc20FiveDecimalTokenAddresses[0]); - break; - case AssetDataScenario.ERC20InvalidAssetProxyId: { - const validAssetData = assetProxyUtils.encodeERC20ProxyData( - this._nonZrxERC20EighteenDecimalTokenAddresses[0], - ); - makerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`; - break; - } - case AssetDataScenario.ERC721ValidAssetProxyId: - makerAssetData = assetProxyUtils.encodeERC721ProxyData( - this._erc721Token.address, - erc721MakerAssetIds[0], - ); + makerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]); break; - case AssetDataScenario.ERC721InvalidAssetProxyId: { - const validAssetData = assetProxyUtils.encodeERC721ProxyData( + case AssetDataScenario.ERC721: + makerAssetData = assetProxyUtils.encodeERC721AssetData( this._erc721Token.address, erc721MakerAssetIds[0], ); - makerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`; break; - } default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', makerAssetDataScenario); + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } - switch (takerAssetDataScenario) { + switch (orderScenario.takerAssetDataScenario) { case AssetDataScenario.ZRXFeeToken: - takerAssetData = assetProxyUtils.encodeERC20ProxyData(this._zrxAddress); + takerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress); break; case AssetDataScenario.ERC20NonZRXEighteenDecimals: - takerAssetData = assetProxyUtils.encodeERC20ProxyData( + takerAssetData = assetProxyUtils.encodeERC20AssetData( this._nonZrxERC20EighteenDecimalTokenAddresses[1], ); break; case AssetDataScenario.ERC20FiveDecimals: - takerAssetData = assetProxyUtils.encodeERC20ProxyData(this._erc20FiveDecimalTokenAddresses[1]); + takerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]); break; - case AssetDataScenario.ERC20InvalidAssetProxyId: { - const validAssetData = assetProxyUtils.encodeERC20ProxyData( - this._nonZrxERC20EighteenDecimalTokenAddresses[1], - ); - takerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`; - break; - } - case AssetDataScenario.ERC721ValidAssetProxyId: - takerAssetData = assetProxyUtils.encodeERC721ProxyData( - this._erc721Token.address, - erc721TakerAssetIds[0], - ); - break; - case AssetDataScenario.ERC721InvalidAssetProxyId: { - const validAssetData = assetProxyUtils.encodeERC721ProxyData( + case AssetDataScenario.ERC721: + takerAssetData = assetProxyUtils.encodeERC721AssetData( this._erc721Token.address, erc721TakerAssetIds[0], ); - takerAssetData = `${validAssetData.slice(0, -2)}${invalidAssetProxyIdHex}`; break; - } default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', takerAssetDataScenario); + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } - switch (makerAssetAmountScenario) { + switch (orderScenario.makerAssetAmountScenario) { case OrderAmountScenario.NonZero: - switch (makerAssetDataScenario) { + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: case AssetDataScenario.ERC20NonZRXEighteenDecimals: - case AssetDataScenario.ERC20InvalidAssetProxyId: makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; break; case AssetDataScenario.ERC20FiveDecimals: makerAssetAmount = TEN_UNITS_FIVE_DECIMALS; break; - case AssetDataScenario.ERC721ValidAssetProxyId: - case AssetDataScenario.ERC721InvalidAssetProxyId: + case AssetDataScenario.ERC721: makerAssetAmount = ONE_NFT_UNIT; break; default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', makerAssetDataScenario); + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } break; case OrderAmountScenario.Zero: makerAssetAmount = new BigNumber(0); break; default: - throw errorUtils.spawnSwitchErr('OrderAmountScenario', makerAssetAmountScenario); + throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.makerAssetAmountScenario); } - switch (takerAssetAmountScenario) { + switch (orderScenario.takerAssetAmountScenario) { case OrderAmountScenario.NonZero: - switch (takerAssetDataScenario) { + switch (orderScenario.takerAssetDataScenario) { case AssetDataScenario.ERC20NonZRXEighteenDecimals: - case AssetDataScenario.ERC20InvalidAssetProxyId: + case AssetDataScenario.ZRXFeeToken: takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; break; case AssetDataScenario.ERC20FiveDecimals: takerAssetAmount = TEN_UNITS_FIVE_DECIMALS; break; - case AssetDataScenario.ERC721ValidAssetProxyId: - case AssetDataScenario.ERC721InvalidAssetProxyId: + case AssetDataScenario.ERC721: takerAssetAmount = ONE_NFT_UNIT; break; default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', takerAssetDataScenario); + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } break; case OrderAmountScenario.Zero: takerAssetAmount = new BigNumber(0); break; default: - throw errorUtils.spawnSwitchErr('OrderAmountScenario', takerAssetAmountScenario); + throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.takerAssetAmountScenario); } - switch (makerFeeScenario) { + switch (orderScenario.makerFeeScenario) { case OrderAmountScenario.NonZero: makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; break; @@ -218,10 +169,10 @@ export class NewOrderFactory { makerFee = new BigNumber(0); break; default: - throw errorUtils.spawnSwitchErr('OrderAmountScenario', makerFeeScenario); + throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.makerFeeScenario); } - switch (takerFeeScenario) { + switch (orderScenario.takerFeeScenario) { case OrderAmountScenario.NonZero: takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; break; @@ -229,18 +180,21 @@ export class NewOrderFactory { takerFee = new BigNumber(0); break; default: - throw errorUtils.spawnSwitchErr('OrderAmountScenario', takerFeeScenario); + throw errorUtils.spawnSwitchErr('OrderAmountScenario', orderScenario.takerFeeScenario); } - switch (expirationTimeSecondsScenario) { + switch (orderScenario.expirationTimeSecondsScenario) { case ExpirationTimeSecondsScenario.InFuture: - expirationTimeSeconds = new BigNumber(Date.now() + TEN_MINUTES_MS); + expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite break; case ExpirationTimeSecondsScenario.InPast: - expirationTimeSeconds = new BigNumber(Date.now() - TEN_MINUTES_MS); + expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970 break; default: - throw errorUtils.spawnSwitchErr('ExpirationTimeSecondsScenario', expirationTimeSecondsScenario); + throw errorUtils.spawnSwitchErr( + 'ExpirationTimeSecondsScenario', + orderScenario.expirationTimeSecondsScenario, + ); } const order: Order = { diff --git a/packages/contracts/src/utils/order_info_utils.ts b/packages/contracts/src/utils/order_info_utils.ts deleted file mode 100644 index 9df627da3..000000000 --- a/packages/contracts/src/utils/order_info_utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { assetProxyUtils, OrderStateUtils } from '@0xproject/order-utils'; -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -import { ExchangeContract } from '../contract_wrappers/generated/exchange'; - -import { constants } from './constants'; -import { ERC20Wrapper } from './erc20_wrapper'; -import { SimpleERC20BalanceAndProxyAllowanceFetcher } from './simple_erc20_balance_and_allowance_fetcher'; -import { SimpleOrderFilledCancelledFetcher } from './simple_filled_cancelled_fetcher'; - -export class OrderInfoUtils { - private _orderStateUtils: OrderStateUtils; - private _erc20Wrapper: ERC20Wrapper; - constructor(exchangeContract: ExchangeContract, erc20Wrapper: ERC20Wrapper, zrxAddress: string) { - this._erc20Wrapper = erc20Wrapper; - const simpleOrderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(exchangeContract, zrxAddress); - const simpleERC20BalanceAndProxyAllowanceFetcher = new SimpleERC20BalanceAndProxyAllowanceFetcher(erc20Wrapper); - this._orderStateUtils = new OrderStateUtils( - simpleERC20BalanceAndProxyAllowanceFetcher, - simpleOrderFilledCancelledFetcher, - ); - } - public async getFillableTakerAssetAmountAsync(signedOrder: SignedOrder, takerAddress: string): Promise { - const orderRelevantState = await this._orderStateUtils.getOrderRelevantStateAsync(signedOrder); - console.log('orderRelevantState', orderRelevantState); - if (takerAddress === constants.NULL_ADDRESS) { - return orderRelevantState.remainingFillableTakerAssetAmount; - } - const takerAssetData = assetProxyUtils.decodeERC20ProxyData(signedOrder.takerAssetData); - const takerBalance = await this._erc20Wrapper.getBalanceAsync(takerAddress, takerAssetData.tokenAddress); - const takerAllowance = await this._erc20Wrapper.getProxyAllowanceAsync( - takerAddress, - takerAssetData.tokenAddress, - ); - // TODO: We also need to make sure taker has sufficient ZRX for fees... - const fillableTakerAssetAmount = BigNumber.min([ - takerBalance, - takerAllowance, - orderRelevantState.remainingFillableTakerAssetAmount, - ]); - return fillableTakerAssetAmount; - } -} diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 2a8791e4c..40643fa75 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -4,6 +4,13 @@ import { BigNumber } from '@0xproject/utils'; import { CancelOrder, MatchOrder } from './types'; export const orderUtils = { + getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + const partialAmount = numerator + .mul(target) + .div(denominator) + .floor(); + return partialAmount; + }, createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => { const fill = { order: orderUtils.getOrderWithoutExchangeAddress(signedOrder), diff --git a/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts new file mode 100644 index 000000000..a295a40c4 --- /dev/null +++ b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts @@ -0,0 +1,19 @@ +import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; + +import { AssetWrapper } from './asset_wrapper'; + +export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { + private _assetWrapper: AssetWrapper; + constructor(assetWrapper: AssetWrapper) { + this._assetWrapper = assetWrapper; + } + public async getBalanceAsync(assetData: string, userAddress: string): Promise { + const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData); + return balance; + } + public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise { + const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData); + return proxyAllowance; + } +} diff --git a/packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts b/packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts deleted file mode 100644 index 6eed9227a..000000000 --- a/packages/contracts/src/utils/simple_erc20_balance_and_allowance_fetcher.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils'; -import { BigNumber } from '@0xproject/utils'; - -import { ERC20Wrapper } from './erc20_wrapper'; - -// TODO(fabio): Refactor this to also work for ERC721! -export class SimpleERC20BalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { - private _erc20TokenWrapper: ERC20Wrapper; - constructor(erc20TokenWrapper: ERC20Wrapper) { - this._erc20TokenWrapper = erc20TokenWrapper; - } - public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise { - const balance = await this._erc20TokenWrapper.getBalanceAsync(userAddress, tokenAddress); - return balance; - } - public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise { - const proxyAllowance = await this._erc20TokenWrapper.getProxyAllowanceAsync(userAddress, tokenAddress); - return proxyAllowance; - } -} diff --git a/packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts b/packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts deleted file mode 100644 index 0a80637cc..000000000 --- a/packages/contracts/src/utils/simple_filled_cancelled_fetcher.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; -import { BigNumber } from '@0xproject/utils'; -import { BlockParamLiteral } from 'ethereum-types'; - -import { - CancelContractEventArgs, - ExchangeContract, - FillContractEventArgs, -} from '../contract_wrappers/generated/exchange'; - -export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { - private _exchangeContract: ExchangeContract; - private _zrxAddress: string; - constructor(exchange: ExchangeContract, zrxAddress: string) { - this._exchangeContract = exchange; - this._zrxAddress = zrxAddress; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise { - const filledTakerAmount = new BigNumber(await this._exchangeContract.filled.callAsync(orderHash)); - return filledTakerAmount; - } - public async isOrderCancelledAsync(orderHash: string): Promise { - const methodOpts = { - defaultBlock: BlockParamLiteral.Latest, - }; - const isCancelled = await this._exchangeContract.cancelled.callAsync(orderHash); - return isCancelled; - } - public getZRXTokenAddress(): string { - return this._zrxAddress; - } -} diff --git a/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts new file mode 100644 index 000000000..24afe36b7 --- /dev/null +++ b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts @@ -0,0 +1,24 @@ +import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; + +import { ExchangeWrapper } from './exchange_wrapper'; + +export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { + private _exchangeWrapper: ExchangeWrapper; + private _zrxAssetData: string; + constructor(exchange: ExchangeWrapper, zrxAssetData: string) { + this._exchangeWrapper = exchange; + this._zrxAssetData = zrxAssetData; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise { + const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash)); + return filledTakerAmount; + } + public async isOrderCancelledAsync(orderHash: string): Promise { + const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash); + return isCancelled; + } + public getZRXAssetData(): string { + return this._zrxAssetData; + } +} diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index a6e1cd6d0..77e8d867e 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -169,10 +169,19 @@ export enum ExpirationTimeSecondsScenario { } export enum AssetDataScenario { - ERC721ValidAssetProxyId = 'ERC721_VALID_ASSET_PROXY_ID', - ERC721InvalidAssetProxyId = 'ERC721_INVALID_ASSET_PROXY_ID', + ERC721 = 'ERC721', ZRXFeeToken = 'ZRX_FEE_TOKEN', - ERC20InvalidAssetProxyId = 'ERC20_INVALID_ASSET_PROXY_ID', ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS', ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS', } + +export interface OrderScenario { + feeRecipientScenario: FeeRecipientAddressScenario; + makerAssetAmountScenario: OrderAmountScenario; + takerAssetAmountScenario: OrderAmountScenario; + makerFeeScenario: OrderAmountScenario; + takerFeeScenario: OrderAmountScenario; + expirationTimeSecondsScenario: ExpirationTimeSecondsScenario; + makerAssetDataScenario: AssetDataScenario; + takerAssetDataScenario: AssetDataScenario; +} -- cgit v1.2.3