From 8ab80914e01ce67020ee2c94e6390309ca1b850f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 8 Jun 2017 11:25:47 +0200 Subject: Implement batchFillOrKill and tests --- src/contract_wrappers/exchange_wrapper.ts | 77 ++++++++++++++++++++++++++++--- src/types.ts | 11 +++++ 2 files changed, 81 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 4f132656e..901ea4749 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -10,6 +10,7 @@ import { OrderValues, OrderAddresses, Order, + OrderFillOrKillRequest, SignedOrder, ContractEvent, ExchangeEvents, @@ -188,13 +189,8 @@ export class ExchangeWrapper extends ContractWrapper { const exchangeInstance = await this.getExchangeContractAsync(); await this.validateFillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress); - // Check that fillValue available >= fillTakerAmount - const orderHashHex = await this.getOrderHashHexAsync(signedOrder); - const unavailableTakerAmount = await this.getUnavailableTakerAmountAsync(orderHashHex); - const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount); - if (remainingTakerAmount < fillTakerAmount) { - throw new Error(ExchangeContractErrs.INSUFFICIENT_REMAINING_FILL_AMOUNT); - } + await this.validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder, exchangeInstance.address, + fillTakerAmount); const [orderAddresses, orderValues] = ExchangeWrapper.getOrderAddressesAndValues(signedOrder); @@ -223,6 +219,62 @@ export class ExchangeWrapper extends ContractWrapper { ); this.throwErrorLogsAsErrors(response.logs); } + /** + * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically + * filled to the desired fillAmount or aborted. + */ + public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[], + takerAddress: string) { + await assert.isSenderAddressAsync('takerAddress', takerAddress, this.web3Wrapper); + const exchangeInstance = await this.getExchangeContractAsync(); + _.each(orderFillOrKillRequests, request => { + assert.doesConformToSchema('signedOrder', + SchemaValidator.convertToJSONSchemaCompatibleObject(request.signedOrder as object), + signedOrderSchema); + assert.isBigNumber('fillTakerAmount', request.fillTakerAmount); + this.validateFillOrKillOrderAndThrowIfInvalidAsync(request.signedOrder, + exchangeInstance.address, + request.fillTakerAmount); + }); + + const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => { + return [ + ...ExchangeWrapper.getOrderAddressesAndValues(request.signedOrder), + request.fillTakerAmount, + request.signedOrder.ecSignature.v, + request.signedOrder.ecSignature.r, + request.signedOrder.ecSignature.s, + ]; + }); + + const [orderAddresses, orderValues, fillTakerAmounts, vParams, rParams, sParams] = + _.unzip(orderAddressesValuesAndTakerTokenFillAmounts); + + const gas = await exchangeInstance.batchFillOrKill.estimateGas( + orderAddresses, + orderValues, + fillTakerAmounts, + vParams, + rParams, + sParams, + { + from: takerAddress, + }, + ); + const response: ContractResponse = await exchangeInstance.batchFillOrKill( + orderAddresses, + orderValues, + fillTakerAmounts, + vParams, + rParams, + sParams, + { + from: takerAddress, + gas, + }, + ); + this.throwErrorLogsAsErrors(response.logs); + } /** * Cancel a given fill amount of an order. Cancellations are cumulative. */ @@ -334,6 +386,17 @@ export class ExchangeWrapper extends ContractWrapper { throw new Error(ExchangeContractErrs.ORDER_CANCEL_EXPIRED); } } + private async validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder, + exchangeAddress: string, + fillTakerAmount: BigNumber.BigNumber) { + // Check that fillValue available >= fillTakerAmount + const orderHashHex = utils.getOrderHashHex(signedOrder, exchangeAddress); + const unavailableTakerAmount = await this.getUnavailableTakerAmountAsync(orderHashHex); + const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount); + if (remainingTakerAmount < fillTakerAmount) { + throw new Error(ExchangeContractErrs.INSUFFICIENT_REMAINING_FILL_AMOUNT); + } + } /** * This method does not currently validate the edge-case where the makerToken or takerToken is also the token used * to pay fees (ZRX). It is possible for them to have enough for fees and the transfer but not both. diff --git a/src/types.ts b/src/types.ts index cc145dc2e..d19fb2d43 100644 --- a/src/types.ts +++ b/src/types.ts @@ -80,6 +80,12 @@ export interface ExchangeContract extends ContractInstance { estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber, v: number, r: string, s: string, txOpts?: TxOpts) => number; }; + batchFillOrKill: { + (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillValuesT: BigNumber.BigNumber[], + v: number[], r: string[], s: string[], txOpts: TxOpts): ContractResponse; + estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillValuesT: BigNumber.BigNumber[], + v: number[], r: string[], s: string[], txOpts?: TxOpts) => number; + }; filled: { call: (orderHash: string) => BigNumber.BigNumber; }; @@ -222,3 +228,8 @@ export interface SubscriptionOpts { } export type DoneCallback = (err?: Error) => void; + +export interface OrderFillOrKillRequest { + signedOrder: SignedOrder; + fillTakerAmount: BigNumber.BigNumber; +} -- cgit