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",
|
||||
"changes": [
|
||||
|
@@ -131,3 +131,21 @@ export function getContractAddressesForNetworkOrThrow(networkId: NetworkId): Con
|
||||
}
|
||||
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,
|
||||
"version": "4.3.1",
|
||||
|
@@ -59,7 +59,6 @@
|
||||
"@0x/base-contract": "^5.3.3",
|
||||
"@0x/contract-addresses": "^3.1.0",
|
||||
"@0x/contract-artifacts": "^2.2.1",
|
||||
"@0x/order-utils": "^8.3.1",
|
||||
"@0x/sol-compiler": "^3.1.14",
|
||||
"@0x/subproviders": "^5.0.3",
|
||||
"@0x/typescript-typings": "^4.2.5",
|
||||
|
@@ -1,15 +1,43 @@
|
||||
import * as wrappers from '@0x/abi-gen-wrappers';
|
||||
import { ContractAddresses } from '@0x/contract-addresses';
|
||||
import * as artifacts from '@0x/contract-artifacts';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
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 { SupportedProvider, TxData } from 'ethereum-types';
|
||||
import { MethodAbi, SupportedProvider, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
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
|
||||
* version of the 0x protocol.
|
||||
@@ -55,7 +83,7 @@ export async function runMigrationsAsync(
|
||||
);
|
||||
|
||||
// Exchange
|
||||
const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
|
||||
const zrxAssetData = encodeERC20AssetData(zrxToken.address);
|
||||
const exchange = await wrappers.ExchangeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Exchange,
|
||||
provider,
|
||||
@@ -173,8 +201,8 @@ export async function runMigrationsAsync(
|
||||
txDefaults,
|
||||
artifacts,
|
||||
exchange.address,
|
||||
assetDataUtils.encodeERC20AssetData(zrxToken.address),
|
||||
assetDataUtils.encodeERC20AssetData(etherToken.address),
|
||||
encodeERC20AssetData(zrxToken.address),
|
||||
encodeERC20AssetData(etherToken.address),
|
||||
);
|
||||
|
||||
// OrderValidator
|
||||
|
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "8.4.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Implement `simpleValidateOrderFillableOrThrowAsync`",
|
||||
"pr": 2096
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1567521715,
|
||||
"version": "8.3.1",
|
||||
|
@@ -13,7 +13,7 @@
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s build test",
|
||||
"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",
|
||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||
"clean": "shx rm -rf lib generated_docs",
|
||||
@@ -40,6 +40,8 @@
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/dev-utils": "^2.3.2",
|
||||
"@0x/migrations": "^4.3.1",
|
||||
"@0x/subproviders": "^5.0.3",
|
||||
"@0x/ts-doc-gen": "^0.0.21",
|
||||
"@0x/tslint-config": "^3.0.1",
|
||||
"@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,
|
||||
OrdersAndRemainingTakerFillAmount,
|
||||
OrdersAndRemainingMakerFillAmount,
|
||||
ValidateOrderFillableOpts,
|
||||
} from './types';
|
||||
|
||||
export { NetworkId } from '@0x/contract-addresses';
|
||||
|
@@ -1,21 +1,29 @@
|
||||
import {
|
||||
DevUtilsContract,
|
||||
ExchangeContract,
|
||||
getContractAddressesForNetworkOrThrow,
|
||||
IAssetProxyContract,
|
||||
NetworkId,
|
||||
} from '@0x/abi-gen-wrappers';
|
||||
import { assert } from '@0x/assert';
|
||||
import { getNetworkIdByExchangeAddressOrThrow } from '@0x/contract-addresses';
|
||||
import { ExchangeContractErrs, RevertReason, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
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 { constants } from './constants';
|
||||
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
||||
import { orderCalculationUtils } from './order_calculation_utils';
|
||||
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 { 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';
|
||||
|
||||
/**
|
||||
@@ -171,6 +179,74 @@ export class OrderValidationUtils {
|
||||
const provider = providerUtils.standardizeOrThrow(supportedProvider);
|
||||
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
|
||||
// to return helpful revert reasons instead of ORDER_UNFILLABLE. Instruct devs
|
||||
// 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;
|
||||
}
|
||||
|
||||
export interface ValidateOrderFillableOpts {
|
||||
expectedFillTakerTokenAmount?: BigNumber;
|
||||
validateRemainingOrderAmountIsFillable?: boolean;
|
||||
simulationTakerAddress?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter.
|
||||
* 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,
|
||||
};
|
||||
|
||||
await blockchainLifecycle.startAsync();
|
||||
const erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ERC20Proxy,
|
||||
provider,
|
||||
@@ -74,6 +75,9 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#transferFromAsync', function(): void {
|
||||
// HACK: For some reason these tests need a slightly longer timeout
|
||||
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 * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { runMigrationsOnceAsync } from '@0x/migrations';
|
||||
|
||||
import { AbstractOrderFilledCancelledFetcher, assetDataUtils, signatureUtils, SignedOrder } from '../src';
|
||||
import { OrderValidationUtils } from '../src/order_validation_utils';
|
||||
|
||||
import { UntransferrableDummyERC20Token } from './artifacts/UntransferrableDummyERC20Token';
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { testOrderFactory } from './utils/test_order_factory';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('OrderValidationUtils', () => {
|
||||
describe('#isRoundingError', () => {
|
||||
@@ -67,4 +77,174 @@ describe('OrderValidationUtils', () => {
|
||||
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 { Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import Web3ProviderEngine = require('web3-provider-engine');
|
||||
|
||||
const provider: Web3ProviderEngine = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
Reference in New Issue
Block a user