Merge pull request #1235 from 0xProject/fixOrderValidation
[order-utils] Fix order validation method
This commit is contained in:
commit
c41622c20a
@ -1,4 +1,24 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "4.0.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Add signature validation, regular cancellation and `cancelledUpTo` checks to `validateOrderFillableOrThrowAsync`",
|
||||||
|
"pr": 1235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Improved the errors thrown by `validateOrderFillableOrThrowAsync` by making them more descriptive",
|
||||||
|
"pr": 1235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Throw previously swallowed network errors when calling `validateOrderFillableOrThrowAsync` (see issue: #1218)",
|
||||||
|
"pr": 1235
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@ -18,6 +18,7 @@ import { OrderFilledCancelledFetcher } from '../fetchers/order_filled_cancelled_
|
|||||||
import { methodOptsSchema } from '../schemas/method_opts_schema';
|
import { methodOptsSchema } from '../schemas/method_opts_schema';
|
||||||
import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
|
import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
|
||||||
import { txOptsSchema } from '../schemas/tx_opts_schema';
|
import { txOptsSchema } from '../schemas/tx_opts_schema';
|
||||||
|
import { validateOrderFillableOptsSchema } from '../schemas/validate_order_fillable_opts_schema';
|
||||||
import {
|
import {
|
||||||
BlockRange,
|
BlockRange,
|
||||||
EventCallback,
|
EventCallback,
|
||||||
@ -1114,6 +1115,9 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
signedOrder: SignedOrder,
|
signedOrder: SignedOrder,
|
||||||
opts: ValidateOrderFillableOpts = {},
|
opts: ValidateOrderFillableOpts = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||||
|
assert.doesConformToSchema('opts', opts, validateOrderFillableOptsSchema);
|
||||||
|
|
||||||
const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher(
|
const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher(
|
||||||
this._erc20TokenWrapper,
|
this._erc20TokenWrapper,
|
||||||
this._erc721TokenWrapper,
|
this._erc721TokenWrapper,
|
||||||
@ -1124,7 +1128,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
|
|
||||||
const expectedFillTakerTokenAmountIfExists = opts.expectedFillTakerTokenAmount;
|
const expectedFillTakerTokenAmountIfExists = opts.expectedFillTakerTokenAmount;
|
||||||
const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest);
|
const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest);
|
||||||
const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher);
|
const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher, this._web3Wrapper.getProvider());
|
||||||
await orderValidationUtils.validateOrderFillableOrThrowAsync(
|
await orderValidationUtils.validateOrderFillableOrThrowAsync(
|
||||||
exchangeTradeSimulator,
|
exchangeTradeSimulator,
|
||||||
signedOrder,
|
signedOrder,
|
||||||
@ -1152,7 +1156,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
|||||||
const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore);
|
const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore);
|
||||||
|
|
||||||
const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest);
|
const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest);
|
||||||
const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher);
|
const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher, this._web3Wrapper.getProvider());
|
||||||
await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||||
exchangeTradeSimulator,
|
exchangeTradeSimulator,
|
||||||
this._web3Wrapper.getProvider(),
|
this._web3Wrapper.getProvider(),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
import { AbstractOrderFilledCancelledFetcher } from '@0x/order-utils';
|
import { AbstractOrderFilledCancelledFetcher, orderHashUtils } from '@0x/order-utils';
|
||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { BlockParamLiteral } from 'ethereum-types';
|
import { BlockParamLiteral } from 'ethereum-types';
|
||||||
|
|
||||||
@ -18,9 +19,18 @@ export class OrderFilledCancelledFetcher implements AbstractOrderFilledCancelled
|
|||||||
});
|
});
|
||||||
return filledTakerAmount;
|
return filledTakerAmount;
|
||||||
}
|
}
|
||||||
public async isOrderCancelledAsync(orderHash: string): Promise<boolean> {
|
public async isOrderCancelledAsync(signedOrder: SignedOrder): Promise<boolean> {
|
||||||
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
const isCancelled = await this._exchange.isCancelledAsync(orderHash);
|
const isCancelled = await this._exchange.isCancelledAsync(orderHash);
|
||||||
return isCancelled;
|
const orderEpoch = await this._exchange.getOrderEpochAsync(
|
||||||
|
signedOrder.makerAddress,
|
||||||
|
signedOrder.senderAddress,
|
||||||
|
{
|
||||||
|
defaultBlock: this._stateLayer,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const isCancelledByOrderEpoch = orderEpoch > signedOrder.salt;
|
||||||
|
return isCancelled || isCancelledByOrderEpoch;
|
||||||
}
|
}
|
||||||
public getZRXAssetData(): string {
|
public getZRXAssetData(): string {
|
||||||
const zrxAssetData = this._exchange.getZRXAssetData();
|
const zrxAssetData = this._exchange.getZRXAssetData();
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
export const validateOrderFillableOptsSchema = {
|
||||||
|
id: '/ValidateOrderFillableOpts',
|
||||||
|
properties: {
|
||||||
|
expectedFillTakerTokenAmount: { $ref: '/wholeNumberSchema' },
|
||||||
|
},
|
||||||
|
type: 'object',
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { BlockchainLifecycle, callbackErrorReporter } from '@0x/dev-utils';
|
import { BlockchainLifecycle, callbackErrorReporter } from '@0x/dev-utils';
|
||||||
import { FillScenarios } from '@0x/fill-scenarios';
|
import { FillScenarios } from '@0x/fill-scenarios';
|
||||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||||
import { DoneCallback, SignedOrder } from '@0x/types';
|
import { DoneCallback, RevertReason, SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { BlockParamLiteral } from 'ethereum-types';
|
import { BlockParamLiteral } from 'ethereum-types';
|
||||||
@ -282,6 +282,19 @@ describe('ExchangeWrapper', () => {
|
|||||||
expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash);
|
expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#validateOrderFillableOrThrowAsync', () => {
|
||||||
|
it('should throw if signature is invalid', async () => {
|
||||||
|
const signedOrderWithInvalidSignature = {
|
||||||
|
...signedOrder,
|
||||||
|
signature:
|
||||||
|
'0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrderWithInvalidSignature),
|
||||||
|
).to.eventually.to.be.rejectedWith(RevertReason.InvalidOrderSignature);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#isValidSignature', () => {
|
describe('#isValidSignature', () => {
|
||||||
it('should check if the signature is valid', async () => {
|
it('should check if the signature is valid', async () => {
|
||||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
|
@ -212,13 +212,17 @@ export class ExchangeWrapper {
|
|||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
|
public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
|
||||||
const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
|
const filledAmount = await this._exchange.filled.callAsync(orderHashHex);
|
||||||
return filledAmount;
|
return filledAmount;
|
||||||
}
|
}
|
||||||
public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
|
public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
|
||||||
const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
|
const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
|
||||||
return isCancelled;
|
return isCancelled;
|
||||||
}
|
}
|
||||||
|
public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise<BigNumber> {
|
||||||
|
const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress);
|
||||||
|
return orderEpoch;
|
||||||
|
}
|
||||||
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
|
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
|
||||||
const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
|
const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
|
||||||
return orderInfo;
|
return orderInfo;
|
||||||
|
@ -392,7 +392,7 @@ export class FillOrderCombinatorialUtils {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected?
|
// 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected?
|
||||||
const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher);
|
const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher, provider);
|
||||||
const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
|
const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
|
||||||
const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
|
const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { AbstractOrderFilledCancelledFetcher } from '@0x/order-utils';
|
import { AbstractOrderFilledCancelledFetcher, orderHashUtils } from '@0x/order-utils';
|
||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
import { ExchangeWrapper } from './exchange_wrapper';
|
import { ExchangeWrapper } from './exchange_wrapper';
|
||||||
@ -14,9 +15,15 @@ export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCan
|
|||||||
const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash));
|
const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash));
|
||||||
return filledTakerAmount;
|
return filledTakerAmount;
|
||||||
}
|
}
|
||||||
public async isOrderCancelledAsync(orderHash: string): Promise<boolean> {
|
public async isOrderCancelledAsync(signedOrder: SignedOrder): Promise<boolean> {
|
||||||
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash);
|
const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash);
|
||||||
return isCancelled;
|
const orderEpoch = await this._exchangeWrapper.getOrderEpochAsync(
|
||||||
|
signedOrder.makerAddress,
|
||||||
|
signedOrder.senderAddress,
|
||||||
|
);
|
||||||
|
const isCancelledByOrderEpoch = orderEpoch > signedOrder.salt;
|
||||||
|
return isCancelled || isCancelledByOrderEpoch;
|
||||||
}
|
}
|
||||||
public getZRXAssetData(): string {
|
public getZRXAssetData(): string {
|
||||||
return this._zrxAssetData;
|
return this._zrxAssetData;
|
||||||
|
@ -1,4 +1,29 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.0.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Add signature validation, regular cancellation and `cancelledUpTo` checks to `validateOrderFillableOrThrowAsync`",
|
||||||
|
"pr": 1235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Improved the errors thrown by `validateOrderFillableOrThrowAsync` by making them more descriptive",
|
||||||
|
"pr": 1235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Throw previously swallowed network errors when calling `validateOrderFillableOrThrowAsync` (see issue: #1218)",
|
||||||
|
"pr": 1235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Modified the `AbstractOrderFilledCancelledFetcher` interface slightly such that `isOrderCancelledAsync` accepts a `signedOrder` instead of an `orderHash` param",
|
||||||
|
"pr": 1235
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,6 +18,6 @@ export abstract class AbstractOrderFilledCancelledFetcher {
|
|||||||
* @param orderHash OrderHash of order we are interested in
|
* @param orderHash OrderHash of order we are interested in
|
||||||
* @return Whether or not the order is cancelled
|
* @return Whether or not the order is cancelled
|
||||||
*/
|
*/
|
||||||
public abstract async isOrderCancelledAsync(orderHash: string): Promise<boolean>;
|
public abstract async isOrderCancelledAsync(signedOrder: SignedOrder): Promise<boolean>;
|
||||||
public abstract getZRXAssetData(): string;
|
public abstract getZRXAssetData(): string;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
export abstract class AbstractOrderFilledCancelledLazyStore {
|
export abstract class AbstractOrderFilledCancelledLazyStore {
|
||||||
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
|
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
|
||||||
public abstract async getIsCancelledAsync(orderHash: string): Promise<boolean>;
|
public abstract async getIsCancelledAsync(signedOrder: SignedOrder): Promise<boolean>;
|
||||||
public abstract setFilledTakerAmount(orderHash: string, balance: BigNumber): void;
|
public abstract setFilledTakerAmount(orderHash: string, balance: BigNumber): void;
|
||||||
public abstract deleteFilledTakerAmount(orderHash: string): void;
|
public abstract deleteFilledTakerAmount(orderHash: string): void;
|
||||||
public abstract setIsCancelled(orderHash: string, isCancelled: boolean): void;
|
public abstract setIsCancelled(orderHash: string, isCancelled: boolean): void;
|
||||||
|
@ -117,7 +117,7 @@ export class OrderStateUtils {
|
|||||||
public async getOpenOrderStateAsync(signedOrder: SignedOrder, transactionHash?: string): Promise<OrderState> {
|
public async getOpenOrderStateAsync(signedOrder: SignedOrder, transactionHash?: string): Promise<OrderState> {
|
||||||
const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder);
|
const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder);
|
||||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
|
const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(signedOrder);
|
||||||
const sidedOrderRelevantState = {
|
const sidedOrderRelevantState = {
|
||||||
isMakerSide: true,
|
isMakerSide: true,
|
||||||
traderBalance: orderRelevantState.makerBalance,
|
traderBalance: orderRelevantState.makerBalance,
|
||||||
@ -256,7 +256,7 @@ export class OrderStateUtils {
|
|||||||
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||||
const totalMakerAssetAmount = signedOrder.makerAssetAmount;
|
const totalMakerAssetAmount = signedOrder.makerAssetAmount;
|
||||||
const totalTakerAssetAmount = signedOrder.takerAssetAmount;
|
const totalTakerAssetAmount = signedOrder.takerAssetAmount;
|
||||||
const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
|
const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(signedOrder);
|
||||||
const remainingTakerAssetAmount = isOrderCancelled
|
const remainingTakerAssetAmount = isOrderCancelled
|
||||||
? new BigNumber(0)
|
? new BigNumber(0)
|
||||||
: totalTakerAssetAmount.minus(filledTakerAssetAmount);
|
: totalTakerAssetAmount.minus(filledTakerAssetAmount);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { RevertReason, SignedOrder } from '@0x/types';
|
import { ExchangeContractErrs, RevertReason, SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { Provider } from 'ethereum-types';
|
import { Provider } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@ -17,6 +17,7 @@ import { utils } from './utils';
|
|||||||
*/
|
*/
|
||||||
export class OrderValidationUtils {
|
export class OrderValidationUtils {
|
||||||
private readonly _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
|
private readonly _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
|
||||||
|
private readonly _provider: Provider;
|
||||||
/**
|
/**
|
||||||
* A Typescript implementation mirroring the implementation of isRoundingError in the
|
* A Typescript implementation mirroring the implementation of isRoundingError in the
|
||||||
* Exchange smart contract
|
* Exchange smart contract
|
||||||
@ -57,65 +58,53 @@ export class OrderValidationUtils {
|
|||||||
senderAddress: string,
|
senderAddress: string,
|
||||||
zrxAssetData: string,
|
zrxAssetData: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
const fillMakerTokenAmount = utils.getPartialAmountFloor(
|
||||||
const fillMakerTokenAmount = utils.getPartialAmountFloor(
|
fillTakerAssetAmount,
|
||||||
fillTakerAssetAmount,
|
signedOrder.takerAssetAmount,
|
||||||
signedOrder.takerAssetAmount,
|
signedOrder.makerAssetAmount,
|
||||||
signedOrder.makerAssetAmount,
|
);
|
||||||
);
|
await exchangeTradeEmulator.transferFromAsync(
|
||||||
await exchangeTradeEmulator.transferFromAsync(
|
signedOrder.makerAssetData,
|
||||||
signedOrder.makerAssetData,
|
signedOrder.makerAddress,
|
||||||
signedOrder.makerAddress,
|
senderAddress,
|
||||||
senderAddress,
|
fillMakerTokenAmount,
|
||||||
fillMakerTokenAmount,
|
TradeSide.Maker,
|
||||||
TradeSide.Maker,
|
TransferType.Trade,
|
||||||
TransferType.Trade,
|
);
|
||||||
);
|
await exchangeTradeEmulator.transferFromAsync(
|
||||||
await exchangeTradeEmulator.transferFromAsync(
|
signedOrder.takerAssetData,
|
||||||
signedOrder.takerAssetData,
|
senderAddress,
|
||||||
senderAddress,
|
signedOrder.makerAddress,
|
||||||
signedOrder.makerAddress,
|
fillTakerAssetAmount,
|
||||||
fillTakerAssetAmount,
|
TradeSide.Taker,
|
||||||
TradeSide.Taker,
|
TransferType.Trade,
|
||||||
TransferType.Trade,
|
);
|
||||||
);
|
const makerFeeAmount = utils.getPartialAmountFloor(
|
||||||
const makerFeeAmount = utils.getPartialAmountFloor(
|
fillTakerAssetAmount,
|
||||||
fillTakerAssetAmount,
|
signedOrder.takerAssetAmount,
|
||||||
signedOrder.takerAssetAmount,
|
signedOrder.makerFee,
|
||||||
signedOrder.makerFee,
|
);
|
||||||
);
|
await exchangeTradeEmulator.transferFromAsync(
|
||||||
await exchangeTradeEmulator.transferFromAsync(
|
zrxAssetData,
|
||||||
zrxAssetData,
|
signedOrder.makerAddress,
|
||||||
signedOrder.makerAddress,
|
signedOrder.feeRecipientAddress,
|
||||||
signedOrder.feeRecipientAddress,
|
makerFeeAmount,
|
||||||
makerFeeAmount,
|
TradeSide.Maker,
|
||||||
TradeSide.Maker,
|
TransferType.Fee,
|
||||||
TransferType.Fee,
|
);
|
||||||
);
|
const takerFeeAmount = utils.getPartialAmountFloor(
|
||||||
const takerFeeAmount = utils.getPartialAmountFloor(
|
fillTakerAssetAmount,
|
||||||
fillTakerAssetAmount,
|
signedOrder.takerAssetAmount,
|
||||||
signedOrder.takerAssetAmount,
|
signedOrder.takerFee,
|
||||||
signedOrder.takerFee,
|
);
|
||||||
);
|
await exchangeTradeEmulator.transferFromAsync(
|
||||||
await exchangeTradeEmulator.transferFromAsync(
|
zrxAssetData,
|
||||||
zrxAssetData,
|
senderAddress,
|
||||||
senderAddress,
|
signedOrder.feeRecipientAddress,
|
||||||
signedOrder.feeRecipientAddress,
|
takerFeeAmount,
|
||||||
takerFeeAmount,
|
TradeSide.Taker,
|
||||||
TradeSide.Taker,
|
TransferType.Fee,
|
||||||
TransferType.Fee,
|
);
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(RevertReason.TransferFailed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static _validateRemainingFillAmountNotZeroOrThrow(
|
|
||||||
takerAssetAmount: BigNumber,
|
|
||||||
filledTakerTokenAmount: BigNumber,
|
|
||||||
): void {
|
|
||||||
if (takerAssetAmount.eq(filledTakerTokenAmount)) {
|
|
||||||
throw new Error(RevertReason.OrderUnfillable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private static _validateOrderNotExpiredOrThrow(expirationTimeSeconds: BigNumber): void {
|
private static _validateOrderNotExpiredOrThrow(expirationTimeSeconds: BigNumber): void {
|
||||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||||
@ -128,9 +117,13 @@ export class OrderValidationUtils {
|
|||||||
* @param orderFilledCancelledFetcher A module that implements the AbstractOrderFilledCancelledFetcher
|
* @param orderFilledCancelledFetcher A module that implements the AbstractOrderFilledCancelledFetcher
|
||||||
* @return An instance of OrderValidationUtils
|
* @return An instance of OrderValidationUtils
|
||||||
*/
|
*/
|
||||||
constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) {
|
constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher, provider: Provider) {
|
||||||
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
|
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
|
||||||
|
this._provider = provider;
|
||||||
}
|
}
|
||||||
|
// TODO(fabio): remove this method once the smart contracts have been refactored
|
||||||
|
// to return helpful revert reasons instead of ORDER_UNFILLABLE. Instruct devs
|
||||||
|
// to make "calls" to validate order fillability + getOrderInfo for fillable amount.
|
||||||
/**
|
/**
|
||||||
* Validate if the supplied order is fillable, and throw if it isn't
|
* Validate if the supplied order is fillable, and throw if it isn't
|
||||||
* @param exchangeTradeEmulator ExchangeTradeEmulator instance
|
* @param exchangeTradeEmulator ExchangeTradeEmulator instance
|
||||||
@ -146,12 +139,29 @@ export class OrderValidationUtils {
|
|||||||
expectedFillTakerTokenAmount?: BigNumber,
|
expectedFillTakerTokenAmount?: BigNumber,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
const isValidSignature = await signatureUtils.isValidSignatureAsync(
|
||||||
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
|
this._provider,
|
||||||
signedOrder.takerAssetAmount,
|
orderHash,
|
||||||
filledTakerTokenAmount,
|
signedOrder.signature,
|
||||||
|
signedOrder.makerAddress,
|
||||||
);
|
);
|
||||||
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
|
if (!isValidSignature) {
|
||||||
|
throw new Error(RevertReason.InvalidOrderSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(signedOrder);
|
||||||
|
if (isCancelled) {
|
||||||
|
throw new Error('CANCELLED');
|
||||||
|
}
|
||||||
|
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||||
|
if (signedOrder.takerAssetAmount.eq(filledTakerTokenAmount)) {
|
||||||
|
throw new Error('FULLY_FILLED');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('EXPIRED');
|
||||||
|
}
|
||||||
let fillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
|
let fillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
|
||||||
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
|
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
|
||||||
fillTakerAssetAmount = expectedFillTakerTokenAmount;
|
fillTakerAssetAmount = expectedFillTakerTokenAmount;
|
||||||
@ -198,10 +208,9 @@ export class OrderValidationUtils {
|
|||||||
throw new Error(OrderError.InvalidSignature);
|
throw new Error(OrderError.InvalidSignature);
|
||||||
}
|
}
|
||||||
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||||
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
|
if (signedOrder.takerAssetAmount.eq(filledTakerTokenAmount)) {
|
||||||
signedOrder.takerAssetAmount,
|
throw new Error(RevertReason.OrderUnfillable);
|
||||||
filledTakerTokenAmount,
|
}
|
||||||
);
|
|
||||||
if (signedOrder.takerAddress !== constants.NULL_ADDRESS && signedOrder.takerAddress !== takerAddress) {
|
if (signedOrder.takerAddress !== constants.NULL_ADDRESS && signedOrder.takerAddress !== takerAddress) {
|
||||||
throw new Error(RevertReason.InvalidTaker);
|
throw new Error(RevertReason.InvalidTaker);
|
||||||
}
|
}
|
||||||
@ -210,13 +219,30 @@ export class OrderValidationUtils {
|
|||||||
const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerAssetAmount)
|
const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerAssetAmount)
|
||||||
? remainingTakerTokenAmount
|
? remainingTakerTokenAmount
|
||||||
: fillTakerAssetAmount;
|
: fillTakerAssetAmount;
|
||||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
try {
|
||||||
exchangeTradeEmulator,
|
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||||
signedOrder,
|
exchangeTradeEmulator,
|
||||||
desiredFillTakerTokenAmount,
|
signedOrder,
|
||||||
takerAddress,
|
desiredFillTakerTokenAmount,
|
||||||
zrxAssetData,
|
takerAddress,
|
||||||
);
|
zrxAssetData,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
const transferFailedErrorMessages = [
|
||||||
|
ExchangeContractErrs.InsufficientMakerBalance,
|
||||||
|
ExchangeContractErrs.InsufficientMakerFeeBalance,
|
||||||
|
ExchangeContractErrs.InsufficientTakerBalance,
|
||||||
|
ExchangeContractErrs.InsufficientTakerFeeBalance,
|
||||||
|
ExchangeContractErrs.InsufficientMakerAllowance,
|
||||||
|
ExchangeContractErrs.InsufficientMakerFeeAllowance,
|
||||||
|
ExchangeContractErrs.InsufficientTakerAllowance,
|
||||||
|
ExchangeContractErrs.InsufficientTakerFeeAllowance,
|
||||||
|
];
|
||||||
|
if (_.includes(transferFailedErrorMessages, err.message)) {
|
||||||
|
throw new Error(RevertReason.TransferFailed);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingErrorFloor(
|
const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingErrorFloor(
|
||||||
desiredFillTakerTokenAmount,
|
desiredFillTakerTokenAmount,
|
||||||
@ -228,33 +254,4 @@ export class OrderValidationUtils {
|
|||||||
}
|
}
|
||||||
return filledTakerTokenAmount;
|
return filledTakerTokenAmount;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Validate a call to fillOrKillOrder and throw if it would fail
|
|
||||||
* @param exchangeTradeEmulator ExchangeTradeEmulator to use
|
|
||||||
* @param provider Web3 provider to use for JSON RPC requests
|
|
||||||
* @param signedOrder SignedOrder of interest
|
|
||||||
* @param fillTakerAssetAmount Amount we'd like to fill the order for
|
|
||||||
* @param takerAddress The taker of the order
|
|
||||||
* @param zrxAssetData ZRX asset data
|
|
||||||
*/
|
|
||||||
public async validateFillOrKillOrderThrowIfInvalidAsync(
|
|
||||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
|
||||||
provider: Provider,
|
|
||||||
signedOrder: SignedOrder,
|
|
||||||
fillTakerAssetAmount: BigNumber,
|
|
||||||
takerAddress: string,
|
|
||||||
zrxAssetData: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
|
|
||||||
exchangeTradeEmulator,
|
|
||||||
provider,
|
|
||||||
signedOrder,
|
|
||||||
fillTakerAssetAmount,
|
|
||||||
takerAddress,
|
|
||||||
zrxAssetData,
|
|
||||||
);
|
|
||||||
if (filledTakerTokenAmount !== fillTakerAssetAmount) {
|
|
||||||
throw new Error(RevertReason.OrderUnfillable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { AbstractOrderFilledCancelledFetcher } from '../abstract/abstract_order_filled_cancelled_fetcher';
|
import { AbstractOrderFilledCancelledFetcher } from '../abstract/abstract_order_filled_cancelled_fetcher';
|
||||||
import { AbstractOrderFilledCancelledLazyStore } from '../abstract/abstract_order_filled_cancelled_lazy_store';
|
import { AbstractOrderFilledCancelledLazyStore } from '../abstract/abstract_order_filled_cancelled_lazy_store';
|
||||||
|
import { orderHashUtils } from '../order_hash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy on read store for balances/proxyAllowances of tokens/accounts
|
* Copy on read store for balances/proxyAllowances of tokens/accounts
|
||||||
@ -58,9 +60,10 @@ export class OrderFilledCancelledLazyStore implements AbstractOrderFilledCancell
|
|||||||
* @param orderHash OrderHash from order of interest
|
* @param orderHash OrderHash from order of interest
|
||||||
* @return Whether the order has been cancelled
|
* @return Whether the order has been cancelled
|
||||||
*/
|
*/
|
||||||
public async getIsCancelledAsync(orderHash: string): Promise<boolean> {
|
public async getIsCancelledAsync(signedOrder: SignedOrder): Promise<boolean> {
|
||||||
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||||
if (_.isUndefined(this._isCancelled[orderHash])) {
|
if (_.isUndefined(this._isCancelled[orderHash])) {
|
||||||
const isCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
|
const isCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(signedOrder);
|
||||||
this.setIsCancelled(orderHash, isCancelled);
|
this.setIsCancelled(orderHash, isCancelled);
|
||||||
}
|
}
|
||||||
const cachedIsCancelled = this._isCancelled[orderHash]; // tslint:disable-line:boolean-naming
|
const cachedIsCancelled = this._isCancelled[orderHash]; // tslint:disable-line:boolean-naming
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SignedOrder } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
@ -33,7 +34,7 @@ describe('OrderStateUtils', () => {
|
|||||||
async getFilledTakerAmountAsync(_orderHash: string): Promise<BigNumber> {
|
async getFilledTakerAmountAsync(_orderHash: string): Promise<BigNumber> {
|
||||||
return filledAmount;
|
return filledAmount;
|
||||||
},
|
},
|
||||||
async isOrderCancelledAsync(_orderHash: string): Promise<boolean> {
|
async isOrderCancelledAsync(_signedOrder: SignedOrder): Promise<boolean> {
|
||||||
return cancelled;
|
return cancelled;
|
||||||
},
|
},
|
||||||
getZRXAssetData(): string {
|
getZRXAssetData(): string {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user