diff options
author | Leonid <logvinov.leon@gmail.com> | 2017-08-24 15:56:34 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-24 15:56:34 +0800 |
commit | e216f2ff6de9b2dd998c37c919b8786f7dff1101 (patch) | |
tree | b6fc54d6a328cc4c307bcf95deea36a609c88b94 /src | |
parent | 18f2a93950500e3efec7ae25e32ff644ebb7efcc (diff) | |
parent | 8d6045c1d5737ff2e62f6f3b906cf639da1aa2ba (diff) | |
download | dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.gz dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.bz2 dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.lz dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.xz dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.zst dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.zip |
Merge pull request #128 from 0xProject/verifyOrder
Add implementation for public order validation functions
Diffstat (limited to 'src')
-rw-r--r-- | src/contract_wrappers/exchange_wrapper.ts | 151 | ||||
-rw-r--r-- | src/types.ts | 1 | ||||
-rw-r--r-- | src/utils/order_validation_utils.ts | 69 |
3 files changed, 150 insertions, 71 deletions
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 7994ee72f..66cf06dee 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -84,7 +84,7 @@ export class ExchangeWrapper extends ContractWrapper { constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) { super(web3Wrapper); this._tokenWrapper = tokenWrapper; - this._orderValidationUtils = new OrderValidationUtils(tokenWrapper); + this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this); this._exchangeLogEventEmitters = []; } /** @@ -161,7 +161,7 @@ export class ExchangeWrapper extends ContractWrapper { await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); const exchangeInstance = await this._getExchangeContractAsync(); - await this._validateFillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress); + await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress); const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); @@ -226,7 +226,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); for (const signedOrder of signedOrders) { - await this._validateFillOrderAndThrowIfInvalidAsync( + await this.validateFillOrderThrowIfInvalidAsync( signedOrder, fillTakerTokenAmount, takerAddress); } if (_.isEmpty(signedOrders)) { @@ -311,7 +311,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); for (const orderFillRequest of orderFillRequests) { - await this._validateFillOrderAndThrowIfInvalidAsync( + await this.validateFillOrderThrowIfInvalidAsync( orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress); } if (_.isEmpty(orderFillRequests)) { @@ -377,9 +377,8 @@ export class ExchangeWrapper extends ContractWrapper { await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); const exchangeInstance = await this._getExchangeContractAsync(); - await this._validateFillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress); - await this._validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount); + await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress); const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); @@ -431,7 +430,8 @@ export class ExchangeWrapper extends ContractWrapper { } const exchangeInstance = await this._getExchangeContractAsync(); for (const request of orderFillOrKillRequests) { - await this._validateFillOrKillOrderAndThrowIfInvalidAsync(request.signedOrder, request.fillTakerAmount); + await this.validateFillOrKillOrderThrowIfInvalidAsync( + request.signedOrder, request.fillTakerAmount, takerAddress); } const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => { @@ -488,7 +488,7 @@ export class ExchangeWrapper extends ContractWrapper { await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); const exchangeInstance = await this._getExchangeContractAsync(); - await this._validateCancelOrderAndThrowIfInvalidAsync(order, cancelTakerTokenAmount); + await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); const gas = await exchangeInstance.cancelOrder.estimateGas( @@ -534,7 +534,7 @@ export class ExchangeWrapper extends ContractWrapper { const maker = makers[0]; await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); for (const cancellationRequest of orderCancellationRequests) { - await this._validateCancelOrderAndThrowIfInvalidAsync( + await this.validateCancelOrderThrowIfInvalidAsync( cancellationRequest.order, cancellationRequest.takerTokenCancelAmount, ); } @@ -626,6 +626,78 @@ export class ExchangeWrapper extends ContractWrapper { const exchangeAddress = exchangeInstance.address; return exchangeAddress; } + /** + * Checks if order fill will succeed and throws an error otherwise. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + */ + public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber.BigNumber, + takerAddress: string): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema); + assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const zrxTokenAddress = await this._getZRXTokenAddressAsync(); + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); + } + /** + * Checks if cancelling a given order will succeed and throws an informative error if it won't. + * @param order An object that conforms to the Order or SignedOrder interface. + * The order you would like to cancel. + * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. + */ + public async validateCancelOrderThrowIfInvalidAsync( + order: Order, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<void> { + assert.doesConformToSchema('order', order, orderSchema); + assert.isBigNumber('cancelTakerTokenAmount', cancelTakerTokenAmount); + const orderHash = utils.getOrderHashHex(order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync( + order, cancelTakerTokenAmount, unavailableTakerTokenAmount); + } + /** + * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + */ + public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber.BigNumber, + takerAddress: string): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema); + assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const zrxTokenAddress = await this._getZRXTokenAddressAsync(); + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress); + } + /** + * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing: + * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`. + * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or + * no decimals can only be filled in quantities and ratios that avoid large rounding errors. + * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill. + * @param takerTokenAmount The order size on the taker side + * @param makerTokenAmount The order size on the maker side + */ + public async isRoundingErrorAsync(fillTakerTokenAmount: BigNumber.BigNumber, + takerTokenAmount: BigNumber.BigNumber, + makerTokenAmount: BigNumber.BigNumber): Promise<boolean> { + assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isBigNumber('takerTokenAmount', takerTokenAmount); + assert.isBigNumber('makerTokenAmount', makerTokenAmount); + const exchangeInstance = await this._getExchangeContractAsync(); + const isRoundingError = await exchangeInstance.isRoundingError.call( + fillTakerTokenAmount, takerTokenAmount, makerTokenAmount, + ); + return isRoundingError; + } private async _invalidateContractInstancesAsync(): Promise<void> { await this.stopWatchingAllEventsAsync(); delete this._exchangeContractIfExists; @@ -653,56 +725,6 @@ export class ExchangeWrapper extends ContractWrapper { const orderHashHex = await exchangeInstance.getOrderHash.call(orderAddresses, orderValues); return orderHashHex; } - private async _validateFillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder, - fillTakerAmount: BigNumber.BigNumber, - senderAddress: string): Promise<void> { - if (fillTakerAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); - } - if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== senderAddress) { - throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); - } - const currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); - if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { - throw new Error(ExchangeContractErrs.OrderFillExpired); - } - const zrxTokenAddress = await this._getZRXTokenAddressAsync(signedOrder.exchangeContractAddress); - await this._orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - signedOrder, fillTakerAmount, senderAddress, zrxTokenAddress, - ); - - const wouldRoundingErrorOccur = await this._isRoundingErrorAsync( - fillTakerAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount, - ); - if (wouldRoundingErrorOccur) { - throw new Error(ExchangeContractErrs.OrderFillRoundingError); - } - } - private async _validateCancelOrderAndThrowIfInvalidAsync( - order: Order, takerTokenCancelAmount: BigNumber.BigNumber): Promise<void> { - if (takerTokenCancelAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderCancelAmountZero); - } - const orderHash = utils.getOrderHashHex(order); - const unavailableAmount = await this.getUnavailableTakerAmountAsync(orderHash); - if (order.takerTokenAmount.minus(unavailableAmount).eq(0)) { - throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); - } - const currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); - if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { - throw new Error(ExchangeContractErrs.OrderCancelExpired); - } - } - private async _validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder, - fillTakerAmount: BigNumber.BigNumber) { - // Check that fillValue available >= fillTakerAmount - const orderHashHex = utils.getOrderHashHex(signedOrder); - const unavailableTakerAmount = await this.getUnavailableTakerAmountAsync(orderHashHex); - const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount); - if (remainingTakerAmount < fillTakerAmount) { - throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount); - } - } private _throwErrorLogsAsErrors(logs: ContractEvent[]): void { const errEvent = _.find(logs, {event: 'LogError'}); if (!_.isUndefined(errEvent)) { @@ -711,15 +733,6 @@ export class ExchangeWrapper extends ContractWrapper { throw new Error(errMessage); } } - private async _isRoundingErrorAsync(numerator: BigNumber.BigNumber, - demoninator: BigNumber.BigNumber, - makerTokenAmount: BigNumber.BigNumber): Promise<boolean> { - const exchangeInstance = await this._getExchangeContractAsync(); - const isRoundingError = await exchangeInstance.isRoundingError.call( - numerator, demoninator, makerTokenAmount, - ); - return isRoundingError; - } private async _getExchangeContractAsync(): Promise<ExchangeContract> { if (!_.isUndefined(this._exchangeContractIfExists)) { return this._exchangeContractIfExists; @@ -728,7 +741,7 @@ export class ExchangeWrapper extends ContractWrapper { this._exchangeContractIfExists = contractInstance as ExchangeContract; return this._exchangeContractIfExists; } - private async _getZRXTokenAddressAsync(exchangeContractAddress: string): Promise<string> { + private async _getZRXTokenAddressAsync(): Promise<string> { const exchangeInstance = await this._getExchangeContractAsync(); const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.call(); return ZRXtokenAddress; diff --git a/src/types.ts b/src/types.ts index 0de87ca71..5ba85a7d5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -180,6 +180,7 @@ export enum ExchangeContractErrs { OrderCancelExpired = 'ORDER_CANCEL_EXPIRED', OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO', OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO', OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', diff --git a/src/utils/order_validation_utils.ts b/src/utils/order_validation_utils.ts index 8a737040c..445ad43f9 100644 --- a/src/utils/order_validation_utils.ts +++ b/src/utils/order_validation_utils.ts @@ -1,10 +1,75 @@ -import {ExchangeContractErrs, SignedOrder} from '../types'; +import {ExchangeContractErrs, SignedOrder, Order} from '../types'; import {TokenWrapper} from '../contract_wrappers/token_wrapper'; +import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper'; +import {utils} from '../utils/utils'; +import {constants} from '../utils/constants'; export class OrderValidationUtils { private tokenWrapper: TokenWrapper; - constructor(tokenWrapper: TokenWrapper) { + private exchangeWrapper: ExchangeWrapper; + constructor(tokenWrapper: TokenWrapper, exchangeWrapper: ExchangeWrapper) { this.tokenWrapper = tokenWrapper; + this.exchangeWrapper = exchangeWrapper; + } + public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber.BigNumber, + takerAddress: string, + zrxTokenAddress: string): Promise<void> { + if (fillTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderFillAmountZero); + } + const orderHash = utils.getOrderHashHex(signedOrder); + const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); + if (signedOrder.makerTokenAmount.eq(unavailableTakerTokenAmount)) { + throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); + } + if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) { + throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); + } + const currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); + if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { + throw new Error(ExchangeContractErrs.OrderFillExpired); + } + await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress, + ); + + const wouldRoundingErrorOccur = await this.exchangeWrapper.isRoundingErrorAsync( + fillTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount, + ); + if (wouldRoundingErrorOccur) { + throw new Error(ExchangeContractErrs.OrderFillRoundingError); + } + } + public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber.BigNumber, + takerAddress: string, + zrxTokenAddress: string): Promise<void> { + await this.validateFillOrderThrowIfInvalidAsync( + signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress, + ); + // Check that fillValue available >= fillTakerAmount + const orderHashHex = utils.getOrderHashHex(signedOrder); + const unavailableTakerAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHashHex); + const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount); + if (remainingTakerAmount < fillTakerTokenAmount) { + throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount); + } + } + public async validateCancelOrderThrowIfInvalidAsync(order: Order, + cancelTakerTokenAmount: BigNumber.BigNumber, + unavailableTakerTokenAmount: BigNumber.BigNumber, + ): Promise<void> { + if (cancelTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderCancelAmountZero); + } + if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) { + throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); + } + const currentUnixTimestampSec = utils.getCurrentUnixTimestamp(); + if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { + throw new Error(ExchangeContractErrs.OrderCancelExpired); + } } public async validateFillOrderBalancesAllowancesThrowIfInvalidAsync( signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, senderAddress: string, zrxTokenAddress: string, |