Make it easier to use validateOrderFillableOrThrowAsync (#2096)
* make it easier to use validateOrderFillableOrThrowAsync * add unit tests, use DevUtils * remove dependency on @0x/order-utils from @0x/migrations
This commit is contained in:
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.2.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Added `getNetworkIdByExchangeAddressOrThrow`",
|
||||||
|
"pr": 2096
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
|||||||
@@ -131,3 +131,21 @@ export function getContractAddressesForNetworkOrThrow(networkId: NetworkId): Con
|
|||||||
}
|
}
|
||||||
return networkToAddresses[networkId];
|
return networkToAddresses[networkId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses a given exchange address to look up the network id that the exchange contract is deployed
|
||||||
|
* on. Only works for Ethereum mainnet or a supported testnet. Throws if the exchange address
|
||||||
|
* does not correspond to a known deployed exchange contract.
|
||||||
|
* @param exchangeAddress The exchange address of concern
|
||||||
|
* @returns The network ID on which the exchange contract is deployed
|
||||||
|
*/
|
||||||
|
export function getNetworkIdByExchangeAddressOrThrow(exchangeAddress: string): NetworkId {
|
||||||
|
for (const networkId of Object.keys(networkToAddresses)) {
|
||||||
|
if (networkToAddresses[networkId as any].exchange === exchangeAddress) {
|
||||||
|
return (networkId as any) as NetworkId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Unknown exchange address (${exchangeAddress}). No known 0x Exchange Contract deployed at this address.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "4.3.2",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Removed dependency on @0x/order-utils",
|
||||||
|
"pr": 2096
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1567521715,
|
"timestamp": 1567521715,
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
|
|||||||
@@ -59,7 +59,6 @@
|
|||||||
"@0x/base-contract": "^5.3.3",
|
"@0x/base-contract": "^5.3.3",
|
||||||
"@0x/contract-addresses": "^3.1.0",
|
"@0x/contract-addresses": "^3.1.0",
|
||||||
"@0x/contract-artifacts": "^2.2.1",
|
"@0x/contract-artifacts": "^2.2.1",
|
||||||
"@0x/order-utils": "^8.3.1",
|
|
||||||
"@0x/sol-compiler": "^3.1.14",
|
"@0x/sol-compiler": "^3.1.14",
|
||||||
"@0x/subproviders": "^5.0.3",
|
"@0x/subproviders": "^5.0.3",
|
||||||
"@0x/typescript-typings": "^4.2.5",
|
"@0x/typescript-typings": "^4.2.5",
|
||||||
|
|||||||
@@ -1,15 +1,43 @@
|
|||||||
import * as wrappers from '@0x/abi-gen-wrappers';
|
import * as wrappers from '@0x/abi-gen-wrappers';
|
||||||
import { ContractAddresses } from '@0x/contract-addresses';
|
import { ContractAddresses } from '@0x/contract-addresses';
|
||||||
import * as artifacts from '@0x/contract-artifacts';
|
import * as artifacts from '@0x/contract-artifacts';
|
||||||
import { assetDataUtils } from '@0x/order-utils';
|
|
||||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||||
import { BigNumber, providerUtils } from '@0x/utils';
|
import { AbiEncoder, BigNumber, providerUtils } from '@0x/utils';
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import { SupportedProvider, TxData } from 'ethereum-types';
|
import { MethodAbi, SupportedProvider, TxData } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { erc20TokenInfo, erc721TokenInfo } from './utils/token_info';
|
import { erc20TokenInfo, erc721TokenInfo } from './utils/token_info';
|
||||||
|
|
||||||
|
// HACK (xianny): Copied from @0x/order-utils to get rid of circular dependency
|
||||||
|
/**
|
||||||
|
* Encodes an ERC20 token address into a hex encoded assetData string, usable in the makerAssetData or
|
||||||
|
* takerAssetData fields in a 0x order.
|
||||||
|
* @param tokenAddress The ERC20 token address to encode
|
||||||
|
* @return The hex encoded assetData string
|
||||||
|
*/
|
||||||
|
function encodeERC20AssetData(tokenAddress: string): string {
|
||||||
|
const ERC20_METHOD_ABI: MethodAbi = {
|
||||||
|
constant: false,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'tokenContract',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'ERC20Token',
|
||||||
|
outputs: [],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function',
|
||||||
|
};
|
||||||
|
const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: true };
|
||||||
|
const abiEncoder = new AbiEncoder.Method(ERC20_METHOD_ABI);
|
||||||
|
const args = [tokenAddress];
|
||||||
|
const assetData = abiEncoder.encode(args, encodingRules);
|
||||||
|
return assetData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and deploys all the contracts that are required for the latest
|
* Creates and deploys all the contracts that are required for the latest
|
||||||
* version of the 0x protocol.
|
* version of the 0x protocol.
|
||||||
@@ -55,7 +83,7 @@ export async function runMigrationsAsync(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Exchange
|
// Exchange
|
||||||
const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
|
const zrxAssetData = encodeERC20AssetData(zrxToken.address);
|
||||||
const exchange = await wrappers.ExchangeContract.deployFrom0xArtifactAsync(
|
const exchange = await wrappers.ExchangeContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.Exchange,
|
artifacts.Exchange,
|
||||||
provider,
|
provider,
|
||||||
@@ -173,8 +201,8 @@ export async function runMigrationsAsync(
|
|||||||
txDefaults,
|
txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
exchange.address,
|
exchange.address,
|
||||||
assetDataUtils.encodeERC20AssetData(zrxToken.address),
|
encodeERC20AssetData(zrxToken.address),
|
||||||
assetDataUtils.encodeERC20AssetData(etherToken.address),
|
encodeERC20AssetData(etherToken.address),
|
||||||
);
|
);
|
||||||
|
|
||||||
// OrderValidator
|
// OrderValidator
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "8.4.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Implement `simpleValidateOrderFillableOrThrowAsync`",
|
||||||
|
"pr": 2096
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1567521715,
|
"timestamp": 1567521715,
|
||||||
"version": "8.3.1",
|
"version": "8.3.1",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"test": "yarn run_mocha",
|
"test": "yarn run_mocha",
|
||||||
"rebuild_and_test": "run-s build test",
|
"rebuild_and_test": "run-s build test",
|
||||||
"test:circleci": "yarn test:coverage",
|
"test:circleci": "yarn test:coverage",
|
||||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit",
|
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --timeout 10000 --bail --exit",
|
||||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||||
"clean": "shx rm -rf lib generated_docs",
|
"clean": "shx rm -rf lib generated_docs",
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
"homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/dev-utils": "^2.3.2",
|
"@0x/dev-utils": "^2.3.2",
|
||||||
|
"@0x/migrations": "^4.3.1",
|
||||||
|
"@0x/subproviders": "^5.0.3",
|
||||||
"@0x/ts-doc-gen": "^0.0.21",
|
"@0x/ts-doc-gen": "^0.0.21",
|
||||||
"@0x/tslint-config": "^3.0.1",
|
"@0x/tslint-config": "^3.0.1",
|
||||||
"@types/bn.js": "^4.11.0",
|
"@types/bn.js": "^4.11.0",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { DevUtilsContract } from '@0x/abi-gen-wrappers';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
|
||||||
|
|
||||||
|
export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
|
||||||
|
private readonly _devUtilsContract: DevUtilsContract;
|
||||||
|
constructor(devUtilsContract: DevUtilsContract) {
|
||||||
|
this._devUtilsContract = devUtilsContract;
|
||||||
|
}
|
||||||
|
public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||||
|
const balance = await this._devUtilsContract.getBalance.callAsync(userAddress, assetData);
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||||
|
const proxyAllowance = await this._devUtilsContract.getAssetProxyAllowance.callAsync(userAddress, assetData);
|
||||||
|
return proxyAllowance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,6 +80,7 @@ export {
|
|||||||
FeeOrdersAndRemainingFeeAmount,
|
FeeOrdersAndRemainingFeeAmount,
|
||||||
OrdersAndRemainingTakerFillAmount,
|
OrdersAndRemainingTakerFillAmount,
|
||||||
OrdersAndRemainingMakerFillAmount,
|
OrdersAndRemainingMakerFillAmount,
|
||||||
|
ValidateOrderFillableOpts,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export { NetworkId } from '@0x/contract-addresses';
|
export { NetworkId } from '@0x/contract-addresses';
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
import {
|
import {
|
||||||
|
DevUtilsContract,
|
||||||
ExchangeContract,
|
ExchangeContract,
|
||||||
getContractAddressesForNetworkOrThrow,
|
getContractAddressesForNetworkOrThrow,
|
||||||
IAssetProxyContract,
|
IAssetProxyContract,
|
||||||
NetworkId,
|
NetworkId,
|
||||||
} from '@0x/abi-gen-wrappers';
|
} from '@0x/abi-gen-wrappers';
|
||||||
|
import { assert } from '@0x/assert';
|
||||||
|
import { getNetworkIdByExchangeAddressOrThrow } from '@0x/contract-addresses';
|
||||||
import { ExchangeContractErrs, RevertReason, SignedOrder } from '@0x/types';
|
import { ExchangeContractErrs, RevertReason, SignedOrder } from '@0x/types';
|
||||||
import { BigNumber, providerUtils } from '@0x/utils';
|
import { BigNumber, providerUtils } from '@0x/utils';
|
||||||
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||||
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 { AssetBalanceAndProxyAllowanceFetcher } from './asset_balance_and_proxy_allowance_fetcher';
|
||||||
import { assetDataUtils } from './asset_data_utils';
|
import { assetDataUtils } from './asset_data_utils';
|
||||||
import { constants } from './constants';
|
import { constants } from './constants';
|
||||||
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
||||||
|
import { orderCalculationUtils } from './order_calculation_utils';
|
||||||
import { orderHashUtils } from './order_hash';
|
import { orderHashUtils } from './order_hash';
|
||||||
|
import { OrderStateUtils } from './order_state_utils';
|
||||||
|
import { validateOrderFillableOptsSchema } from './schemas/validate_order_fillable_opts_schema';
|
||||||
import { signatureUtils } from './signature_utils';
|
import { signatureUtils } from './signature_utils';
|
||||||
import { TradeSide, TransferType, TypedDataError } from './types';
|
import { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
|
||||||
|
import { TradeSide, TransferType, TypedDataError, ValidateOrderFillableOpts } from './types';
|
||||||
import { utils } from './utils';
|
import { utils } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,6 +179,74 @@ export class OrderValidationUtils {
|
|||||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||||
this._provider = provider;
|
this._provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(xianny): 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.
|
||||||
|
// This method recreates functionality from ExchangeWrapper (@0x/contract-wrappers < 11.0.0)
|
||||||
|
// to make migrating easier in the interim.
|
||||||
|
/**
|
||||||
|
* Validate if the supplied order is fillable, and throw if it isn't
|
||||||
|
* @param provider The same provider used to interact with contracts
|
||||||
|
* @param signedOrder SignedOrder of interest
|
||||||
|
* @param opts ValidateOrderFillableOpts options (e.g expectedFillTakerTokenAmount.
|
||||||
|
* If it isn't supplied, we check if the order is fillable for the remaining amount.
|
||||||
|
* To check if the order is fillable for a non-zero amount, set `validateRemainingOrderAmountIsFillable` to false.)
|
||||||
|
*/
|
||||||
|
public async simpleValidateOrderFillableOrThrowAsync(
|
||||||
|
provider: SupportedProvider,
|
||||||
|
signedOrder: SignedOrder,
|
||||||
|
opts: ValidateOrderFillableOpts = {},
|
||||||
|
): Promise<void> {
|
||||||
|
assert.doesConformToSchema('opts', opts, validateOrderFillableOptsSchema);
|
||||||
|
|
||||||
|
const exchangeAddress = signedOrder.exchangeAddress;
|
||||||
|
const networkId = getNetworkIdByExchangeAddressOrThrow(exchangeAddress);
|
||||||
|
const { zrxToken, devUtils } = getContractAddressesForNetworkOrThrow(networkId);
|
||||||
|
const exchangeContract = new ExchangeContract(exchangeAddress, provider);
|
||||||
|
const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher(
|
||||||
|
new DevUtilsContract(devUtils, provider),
|
||||||
|
);
|
||||||
|
const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher);
|
||||||
|
const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore);
|
||||||
|
|
||||||
|
// Define fillable taker asset amount
|
||||||
|
let fillableTakerAssetAmount;
|
||||||
|
const shouldValidateRemainingOrderAmountIsFillable =
|
||||||
|
opts.validateRemainingOrderAmountIsFillable === undefined
|
||||||
|
? true
|
||||||
|
: opts.validateRemainingOrderAmountIsFillable;
|
||||||
|
if (opts.expectedFillTakerTokenAmount) {
|
||||||
|
// If the caller has specified a taker fill amount, we use this for all validation
|
||||||
|
fillableTakerAssetAmount = opts.expectedFillTakerTokenAmount;
|
||||||
|
} else if (shouldValidateRemainingOrderAmountIsFillable) {
|
||||||
|
// Default behaviour is to validate the amount left on the order.
|
||||||
|
const filledTakerTokenAmount = await exchangeContract.filled.callAsync(
|
||||||
|
orderHashUtils.getOrderHashHex(signedOrder),
|
||||||
|
);
|
||||||
|
fillableTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
|
||||||
|
} else {
|
||||||
|
const orderStateUtils = new OrderStateUtils(balanceAllowanceStore, this._orderFilledCancelledFetcher);
|
||||||
|
// Calculate the taker amount fillable given the maker balance and allowance
|
||||||
|
const orderRelevantState = await orderStateUtils.getOpenOrderRelevantStateAsync(signedOrder);
|
||||||
|
fillableTakerAssetAmount = orderRelevantState.remainingFillableTakerAssetAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.validateOrderFillableOrThrowAsync(
|
||||||
|
exchangeTradeSimulator,
|
||||||
|
signedOrder,
|
||||||
|
assetDataUtils.encodeERC20AssetData(zrxToken),
|
||||||
|
fillableTakerAssetAmount,
|
||||||
|
);
|
||||||
|
const makerTransferAmount = orderCalculationUtils.getMakerFillAmount(signedOrder, fillableTakerAssetAmount);
|
||||||
|
await OrderValidationUtils.validateMakerTransferThrowIfInvalidAsync(
|
||||||
|
networkId,
|
||||||
|
provider,
|
||||||
|
signedOrder,
|
||||||
|
makerTransferAmount,
|
||||||
|
opts.simulationTakerAddress,
|
||||||
|
);
|
||||||
|
}
|
||||||
// TODO(fabio): remove this method once the smart contracts have been refactored
|
// TODO(fabio): remove this method once the smart contracts have been refactored
|
||||||
// to return helpful revert reasons instead of ORDER_UNFILLABLE. Instruct devs
|
// to return helpful revert reasons instead of ORDER_UNFILLABLE. Instruct devs
|
||||||
// to make "calls" to validate order fillability + getOrderInfo for fillable amount.
|
// to make "calls" to validate order fillability + getOrderInfo for fillable amount.
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const validateOrderFillableOptsSchema = {
|
||||||
|
id: '/ValidateOrderFillableOpts',
|
||||||
|
properties: {
|
||||||
|
expectedFillTakerTokenAmount: { $ref: '/wholeNumberSchema' },
|
||||||
|
},
|
||||||
|
type: 'object',
|
||||||
|
};
|
||||||
@@ -25,6 +25,12 @@ export interface CreateOrderOpts {
|
|||||||
expirationTimeSeconds?: BigNumber;
|
expirationTimeSeconds?: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ValidateOrderFillableOpts {
|
||||||
|
expectedFillTakerTokenAmount?: BigNumber;
|
||||||
|
validateRemainingOrderAmountIsFillable?: boolean;
|
||||||
|
simulationTakerAddress?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
|
* remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
|
||||||
* You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values.
|
* You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values.
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ describe('ExchangeTransferSimulator', async () => {
|
|||||||
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await blockchainLifecycle.startAsync();
|
||||||
const erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
const erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.ERC20Proxy,
|
artifacts.ERC20Proxy,
|
||||||
provider,
|
provider,
|
||||||
@@ -74,6 +75,9 @@ describe('ExchangeTransferSimulator', async () => {
|
|||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await blockchainLifecycle.revertAsync();
|
await blockchainLifecycle.revertAsync();
|
||||||
});
|
});
|
||||||
|
after(async () => {
|
||||||
|
await blockchainLifecycle.revertAsync();
|
||||||
|
});
|
||||||
describe('#transferFromAsync', function(): void {
|
describe('#transferFromAsync', function(): void {
|
||||||
// HACK: For some reason these tests need a slightly longer timeout
|
// HACK: For some reason these tests need a slightly longer timeout
|
||||||
const mochaTestTimeoutMs = 3000;
|
const mochaTestTimeoutMs = 3000;
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
|
import { ContractAddresses, DummyERC20TokenContract } from '@0x/abi-gen-wrappers';
|
||||||
|
import { BlockchainLifecycle, devConstants, tokenUtils } from '@0x/dev-utils';
|
||||||
|
import { ExchangeContractErrs, RevertReason } 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';
|
||||||
|
|
||||||
|
import { runMigrationsOnceAsync } from '@0x/migrations';
|
||||||
|
|
||||||
|
import { AbstractOrderFilledCancelledFetcher, assetDataUtils, signatureUtils, SignedOrder } from '../src';
|
||||||
import { OrderValidationUtils } from '../src/order_validation_utils';
|
import { OrderValidationUtils } from '../src/order_validation_utils';
|
||||||
|
|
||||||
|
import { UntransferrableDummyERC20Token } from './artifacts/UntransferrableDummyERC20Token';
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
import { testOrderFactory } from './utils/test_order_factory';
|
||||||
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||||
|
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
|
|
||||||
describe('OrderValidationUtils', () => {
|
describe('OrderValidationUtils', () => {
|
||||||
describe('#isRoundingError', () => {
|
describe('#isRoundingError', () => {
|
||||||
@@ -67,4 +77,174 @@ describe('OrderValidationUtils', () => {
|
|||||||
expect(isRoundingError).to.be.false();
|
expect(isRoundingError).to.be.false();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#validateOrderFillableOrThrowAsync', () => {
|
||||||
|
let contractAddresses: ContractAddresses;
|
||||||
|
let orderValidationUtils: OrderValidationUtils;
|
||||||
|
let makerAddress: string;
|
||||||
|
let takerAddress: string;
|
||||||
|
let ownerAddress: string;
|
||||||
|
let signedOrder: SignedOrder;
|
||||||
|
let makerTokenContract: DummyERC20TokenContract;
|
||||||
|
let takerTokenContract: DummyERC20TokenContract;
|
||||||
|
const txDefaults = {
|
||||||
|
gas: devConstants.GAS_LIMIT,
|
||||||
|
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||||
|
};
|
||||||
|
before(async () => {
|
||||||
|
contractAddresses = await runMigrationsOnceAsync(provider, txDefaults);
|
||||||
|
await blockchainLifecycle.startAsync();
|
||||||
|
|
||||||
|
const [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
||||||
|
makerTokenContract = new DummyERC20TokenContract(makerTokenAddress, provider, txDefaults);
|
||||||
|
takerTokenContract = new DummyERC20TokenContract(takerTokenAddress, provider, txDefaults);
|
||||||
|
[ownerAddress, makerAddress, takerAddress] = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
|
|
||||||
|
// create signed order
|
||||||
|
const [makerAssetData, takerAssetData] = [
|
||||||
|
assetDataUtils.encodeERC20AssetData(makerTokenContract.address),
|
||||||
|
assetDataUtils.encodeERC20AssetData(takerTokenContract.address),
|
||||||
|
];
|
||||||
|
const defaultOrderParams = {
|
||||||
|
exchangeAddress: contractAddresses.exchange,
|
||||||
|
makerAddress,
|
||||||
|
takerAddress,
|
||||||
|
makerAssetData,
|
||||||
|
takerAssetData,
|
||||||
|
};
|
||||||
|
const makerAssetAmount = new BigNumber(10);
|
||||||
|
const takerAssetAmount = new BigNumber(10000000000000000);
|
||||||
|
const [order] = testOrderFactory.generateTestSignedOrders(
|
||||||
|
{
|
||||||
|
...defaultOrderParams,
|
||||||
|
makerAssetAmount,
|
||||||
|
takerAssetAmount,
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
signedOrder = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress);
|
||||||
|
|
||||||
|
// instantiate OrderValidationUtils
|
||||||
|
const mockOrderFilledFetcher: AbstractOrderFilledCancelledFetcher = {
|
||||||
|
async getFilledTakerAmountAsync(_orderHash: string): Promise<BigNumber> {
|
||||||
|
return new BigNumber(0);
|
||||||
|
},
|
||||||
|
async isOrderCancelledAsync(_signedOrder: SignedOrder): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getZRXAssetData(): string {
|
||||||
|
return assetDataUtils.encodeERC20AssetData(contractAddresses.zrxToken);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
orderValidationUtils = new OrderValidationUtils(mockOrderFilledFetcher, provider);
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await blockchainLifecycle.revertAsync();
|
||||||
|
});
|
||||||
|
beforeEach(async () => {
|
||||||
|
await blockchainLifecycle.startAsync();
|
||||||
|
await makerTokenContract.setBalance.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
signedOrder.makerAssetAmount,
|
||||||
|
);
|
||||||
|
await takerTokenContract.setBalance.awaitTransactionSuccessAsync(
|
||||||
|
takerAddress,
|
||||||
|
signedOrder.takerAssetAmount,
|
||||||
|
);
|
||||||
|
await makerTokenContract.approve.awaitTransactionSuccessAsync(
|
||||||
|
contractAddresses.erc20Proxy,
|
||||||
|
signedOrder.makerAssetAmount,
|
||||||
|
{ from: makerAddress },
|
||||||
|
);
|
||||||
|
await takerTokenContract.approve.awaitTransactionSuccessAsync(
|
||||||
|
contractAddresses.erc20Proxy,
|
||||||
|
signedOrder.takerAssetAmount,
|
||||||
|
{ from: takerAddress },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
afterEach(async () => {
|
||||||
|
await blockchainLifecycle.revertAsync();
|
||||||
|
});
|
||||||
|
it('should throw if signature is invalid', async () => {
|
||||||
|
const signedOrderWithInvalidSignature = {
|
||||||
|
...signedOrder,
|
||||||
|
signature:
|
||||||
|
'0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
|
||||||
|
};
|
||||||
|
|
||||||
|
return expect(
|
||||||
|
orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrderWithInvalidSignature),
|
||||||
|
).to.be.rejectedWith(RevertReason.InvalidOrderSignature);
|
||||||
|
});
|
||||||
|
it('should validate the order with the current balances and allowances for the maker', async () => {
|
||||||
|
await orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrder, {
|
||||||
|
validateRemainingOrderAmountIsFillable: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should validate the order with remaining fillable amount for the order', async () => {
|
||||||
|
await orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrder);
|
||||||
|
});
|
||||||
|
it('should validate the order with specified amount', async () => {
|
||||||
|
await orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrder, {
|
||||||
|
expectedFillTakerTokenAmount: signedOrder.takerAssetAmount,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should throw if the amount is greater than the allowance/balance', async () => {
|
||||||
|
return expect(
|
||||||
|
orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrder, {
|
||||||
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
|
expectedFillTakerTokenAmount: new BigNumber(2).pow(256).minus(1),
|
||||||
|
}),
|
||||||
|
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerAllowance);
|
||||||
|
});
|
||||||
|
it('should throw when the maker does not have enough balance for the remaining order amount', async () => {
|
||||||
|
await makerTokenContract.setBalance.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
signedOrder.makerAssetAmount.minus(1),
|
||||||
|
);
|
||||||
|
return expect(
|
||||||
|
orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrder),
|
||||||
|
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
|
||||||
|
});
|
||||||
|
it('should validate the order when remaining order amount has some fillable amount', async () => {
|
||||||
|
await makerTokenContract.setBalance.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
signedOrder.makerAssetAmount.minus(1),
|
||||||
|
);
|
||||||
|
await orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, signedOrder, {
|
||||||
|
validateRemainingOrderAmountIsFillable: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should throw when the ERC20 token has transfer restrictions', async () => {
|
||||||
|
const artifactDependencies = {};
|
||||||
|
const untransferrableToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||||
|
UntransferrableDummyERC20Token,
|
||||||
|
provider,
|
||||||
|
{ from: ownerAddress },
|
||||||
|
artifactDependencies,
|
||||||
|
'UntransferrableToken',
|
||||||
|
'UTT',
|
||||||
|
new BigNumber(18),
|
||||||
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
|
new BigNumber(2).pow(20).minus(1),
|
||||||
|
);
|
||||||
|
const untransferrableMakerAssetData = assetDataUtils.encodeERC20AssetData(untransferrableToken.address);
|
||||||
|
const invalidOrder = {
|
||||||
|
...signedOrder,
|
||||||
|
makerAssetData: untransferrableMakerAssetData,
|
||||||
|
};
|
||||||
|
const invalidSignedOrder = await signatureUtils.ecSignOrderAsync(provider, invalidOrder, makerAddress);
|
||||||
|
await untransferrableToken.setBalance.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
invalidSignedOrder.makerAssetAmount.plus(1),
|
||||||
|
);
|
||||||
|
await untransferrableToken.approve.awaitTransactionSuccessAsync(
|
||||||
|
contractAddresses.erc20Proxy,
|
||||||
|
invalidSignedOrder.makerAssetAmount.plus(1),
|
||||||
|
{ from: makerAddress },
|
||||||
|
);
|
||||||
|
return expect(
|
||||||
|
orderValidationUtils.simpleValidateOrderFillableOrThrowAsync(provider, invalidSignedOrder),
|
||||||
|
).to.be.rejectedWith(RevertReason.TransferFailed);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { web3Factory } from '@0x/dev-utils';
|
import { web3Factory } from '@0x/dev-utils';
|
||||||
|
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import Web3ProviderEngine = require('web3-provider-engine');
|
|
||||||
|
|
||||||
const provider: Web3ProviderEngine = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
const provider: Web3ProviderEngine = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||||
const web3Wrapper = new Web3Wrapper(provider);
|
const web3Wrapper = new Web3Wrapper(provider);
|
||||||
|
|||||||
Reference in New Issue
Block a user