diff options
author | Leonid Logvinov <logvinov.leon@gmail.com> | 2017-06-08 17:31:40 +0800 |
---|---|---|
committer | Leonid Logvinov <logvinov.leon@gmail.com> | 2017-06-08 17:31:40 +0800 |
commit | c1322717802d652090fb374ac706cb1abf6c26be (patch) | |
tree | efc3905da0882f1e23b896f06a59e48122bd3532 /src | |
parent | 5d464d87e07003c231fca55e87b95cc7e1e9e978 (diff) | |
parent | c6645f9f4f470323086d9004cf603a68091d71a3 (diff) | |
download | dexon-0x-contracts-c1322717802d652090fb374ac706cb1abf6c26be.tar.gz dexon-0x-contracts-c1322717802d652090fb374ac706cb1abf6c26be.tar.zst dexon-0x-contracts-c1322717802d652090fb374ac706cb1abf6c26be.zip |
Merge branch 'master' into batchFillAsync
Diffstat (limited to 'src')
-rw-r--r-- | src/0x.js.ts | 9 | ||||
-rw-r--r-- | src/contract_wrappers/exchange_wrapper.ts | 131 | ||||
-rw-r--r-- | src/types.ts | 9 |
3 files changed, 119 insertions, 30 deletions
diff --git a/src/0x.js.ts b/src/0x.js.ts index 7b53b70ea..8f1178b2a 100644 --- a/src/0x.js.ts +++ b/src/0x.js.ts @@ -130,11 +130,12 @@ export class ZeroEx { * Computes the orderHash for a given order and returns it as a hex encoded string. */ public async getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> { - assert.doesConformToSchema( - 'order', SchemaValidator.convertToJSONSchemaCompatibleObject(order as object), orderSchema); + assert.doesConformToSchema('order', SchemaValidator.convertToJSONSchemaCompatibleObject(order as object), + orderSchema); + const exchangeContractAddr = await this.getExchangeAddressAsync(); - const hashHex = utils.getOrderHashHex(order, exchangeContractAddr); - return hashHex; + const orderHashHex = utils.getOrderHashHex(order, exchangeContractAddr); + return orderHashHex; } /** * Signs an orderHash and returns it's elliptic curve signature diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index dffb9df13..8d95c9fae 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -18,7 +18,9 @@ import { CreateContractEvent, ContractEventObj, EventCallback, - ContractResponse, OrderCancellationRequest, OrderFillRequest, + ContractResponse, + OrderCancellationRequest, + OrderFillRequest, } from '../types'; import {assert} from '../utils/assert'; import {utils} from '../utils/utils'; @@ -235,41 +237,121 @@ export class ExchangeWrapper extends ContractWrapper { this.throwErrorLogsAsErrors(response.logs); } /** + * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, + * the fill order is abandoned. + */ + public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, + takerAddress: string) { + assert.doesConformToSchema('signedOrder', + SchemaValidator.convertToJSONSchemaCompatibleObject(signedOrder as object), + signedOrderSchema); + assert.isBigNumber('fillTakerAmount', fillTakerAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this.web3Wrapper); + + 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); + } + + const [orderAddresses, orderValues] = ExchangeWrapper.getOrderAddressesAndValues(signedOrder); + + const gas = await exchangeInstance.fillOrKill.estimateGas( + orderAddresses, + orderValues, + fillTakerAmount, + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + { + from: takerAddress, + }, + ); + const response: ContractResponse = await exchangeInstance.fillOrKill( + orderAddresses, + orderValues, + fillTakerAmount, + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + { + from: takerAddress, + gas, + }, + ); + this.throwErrorLogsAsErrors(response.logs); + } + /** * Cancel a given fill amount of an order. Cancellations are cumulative. */ public async cancelOrderAsync( order: Order|SignedOrder, takerTokenCancelAmount: BigNumber.BigNumber): Promise<void> { - await this.batchCancelOrderAsync([{ - order, + assert.doesConformToSchema('order', + SchemaValidator.convertToJSONSchemaCompatibleObject(order as object), + orderSchema); + assert.isBigNumber('takerTokenCancelAmount', takerTokenCancelAmount); + await assert.isSenderAddressAvailableAsync(this.web3Wrapper, 'order.maker', order.maker); + + const exchangeInstance = await this.getExchangeContractAsync(); + await this.validateCancelOrderAndThrowIfInvalidAsync(order, takerTokenCancelAmount); + + const [orderAddresses, orderValues] = ExchangeWrapper.getOrderAddressesAndValues(order); + const gas = await exchangeInstance.cancel.estimateGas( + orderAddresses, + orderValues, + takerTokenCancelAmount, + { + from: order.maker, + }, + ); + const response: ContractResponse = await exchangeInstance.cancel( + orderAddresses, + orderValues, takerTokenCancelAmount, - }]); + { + from: order.maker, + gas, + }, + ); + this.throwErrorLogsAsErrors(response.logs); } /** * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. + * All orders must be from the same maker. */ - public async batchCancelOrderAsync(cancellationRequestsBatch: OrderCancellationRequest[]): Promise<void> { - const makers = _.map(cancellationRequestsBatch, cancellationRequest => cancellationRequest.order.maker); - assert.assert(!_.isEmpty(cancellationRequestsBatch), 'Can not cancel an empty batch'); - assert.assert(_.uniq(makers).length === 1, 'Can not cancel orders from multiple makers in a single batch'); + public async batchCancelOrderAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> { + if (_.isEmpty(orderCancellationRequests)) { + return; // no-op + } + const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker); + assert.assert(_.uniq(makers).length === 1, ExchangeContractErrs.MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH); const maker = makers[0]; - _.forEach(cancellationRequestsBatch, - async (cancellationRequest: OrderCancellationRequest) => { - assert.doesConformToSchema('order', - SchemaValidator.convertToJSONSchemaCompatibleObject(cancellationRequest.order as object), orderSchema); - assert.isBigNumber('takerTokenCancelAmount', cancellationRequest.takerTokenCancelAmount); - await assert.isSenderAddressAvailableAsync(this.web3Wrapper, 'order.maker', - cancellationRequest.order.maker); + await assert.isSenderAddressAvailableAsync(this.web3Wrapper, 'maker', maker); + _.forEach(orderCancellationRequests, + async (cancellationRequest: OrderCancellationRequest, i: number) => { + assert.doesConformToSchema(`orderCancellationRequests[${i}].order`, + SchemaValidator.convertToJSONSchemaCompatibleObject(cancellationRequest.order as object), orderSchema, + ); + assert.isBigNumber(`orderCancellationRequests[${i}].takerTokenCancelAmount`, + cancellationRequest.takerTokenCancelAmount, + ); await this.validateCancelOrderAndThrowIfInvalidAsync( - cancellationRequest.order, cancellationRequest.takerTokenCancelAmount); + cancellationRequest.order, cancellationRequest.takerTokenCancelAmount, + ); }); const exchangeInstance = await this.getExchangeContractAsync(); - const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(cancellationRequestsBatch, cancellationRequest => { + const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { return [ ...ExchangeWrapper.getOrderAddressesAndValues(cancellationRequest.order), cancellationRequest.takerTokenCancelAmount, ]; }); - // _.unzip doesn't type check if values have different types :'( + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( const [orderAddresses, orderValues, takerTokenCancelAmounts] = _.unzip<any>(orderAddressesValuesAndTakerTokenCancelAmounts); const gas = await exchangeInstance.batchCancel.estimateGas( @@ -317,11 +399,10 @@ export class ExchangeWrapper extends ContractWrapper { logEventObj.watch(callback); this.exchangeLogEventObjs.push(logEventObj); } - private async getOrderHashAsync(order: Order|SignedOrder): Promise<string> { - const [orderAddresses, orderValues] = ExchangeWrapper.getOrderAddressesAndValues(order); + private async getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> { const exchangeInstance = await this.getExchangeContractAsync(); - const orderHash = utils.getOrderHashHex(order, exchangeInstance.address); - return orderHash; + const orderHashHex = utils.getOrderHashHex(order, exchangeInstance.address); + return orderHashHex; } private async stopWatchingExchangeLogEventsAsync() { const stopWatchingPromises = _.map(this.exchangeLogEventObjs, logEventObj => { @@ -359,7 +440,7 @@ export class ExchangeWrapper extends ContractWrapper { if (takerTokenCancelAmount.eq(0)) { throw new Error(ExchangeContractErrs.ORDER_CANCEL_AMOUNT_ZERO); } - const orderHash = await this.getOrderHashAsync(order); + const orderHash = await this.getOrderHashHexAsync(order); const unavailableAmount = await this.getUnavailableTakerAmountAsync(orderHash); if (order.takerTokenAmount.minus(unavailableAmount).eq(0)) { throw new Error(ExchangeContractErrs.ORDER_ALREADY_CANCELLED_OR_FILLED); @@ -375,8 +456,8 @@ export class ExchangeWrapper extends ContractWrapper { * Handling the edge-cases that arise when this happens would require making sure that the user has sufficient * funds to pay both the fees and the transfer amount. We decided to punt on this for now as the contracts * will throw for these edge-cases. - * TODO: Throw errors before calling the smart contract for these edge-cases - * TODO: in order to minimize the callers gas costs. + * TODO: Throw errors before calling the smart contract for these edge-cases in order to minimize + * the callers gas costs. */ private async validateFillOrderBalancesAndAllowancesAndThrowIfInvalidAsync(signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, diff --git a/src/types.ts b/src/types.ts index f3cfa440c..90e64bc46 100644 --- a/src/types.ts +++ b/src/types.ts @@ -86,6 +86,12 @@ export interface ExchangeContract extends ContractInstance { estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelAmount: BigNumber.BigNumber[], txOpts?: TxOpts) => number; }; + fillOrKill: { + (orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber, + v: number, r: string, s: string, txOpts?: TxOpts): ContractResponse; + estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber, + v: number, r: string, s: string, txOpts?: TxOpts) => number; + }; filled: { call: (orderHash: string) => BigNumber.BigNumber; }; @@ -151,7 +157,8 @@ export const ExchangeContractErrs = strEnum([ 'INSUFFICIENT_MAKER_FEE_BALANCE', 'INSUFFICIENT_MAKER_FEE_ALLOWANCE', 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', - + 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH', + 'INSUFFICIENT_REMAINING_FILL_AMOUNT', ]); export type ExchangeContractErrs = keyof typeof ExchangeContractErrs; |