protocol/packages/asset-swapper/test/order_state_utils_test.ts
Jacob Evans aae93bb6a7
fix: asset-swapper yield to the event loop every mix path (#2637)
* fix: allow a empty overrides to signal no default

* fix: asset-swapper yield to the event loop every mix path

* fix: optimizations skip order find if Native excluded

* changelogs

* chore: update protocol fee multiplier

* fix: tests async
2020-07-21 17:06:05 +10:00

274 lines
12 KiB
TypeScript

import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract, ERC20TokenContract, ExchangeContract } from '@0x/contract-wrappers';
import { constants as devConstants, getLatestBlockTimestampAsync, OrderFactory } from '@0x/contracts-test-utils';
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
import { migrateOnceAsync } from '@0x/migrations';
import { assetDataUtils } from '@0x/order-utils';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import 'mocha';
import { constants } from '../src/constants';
import { SignedOrderWithFillableAmounts } from '../src/types';
import { OrderStateUtils } from '../src/utils/order_state_utils';
import { chaiSetup } from './utils/chai_setup';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
const PROTOCOL_FEE_MULTIPLIER = 70000;
const PROTOCOL_FEE_PER_FILL = GAS_PRICE.times(PROTOCOL_FEE_MULTIPLIER);
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
const isSignedOrdersWithFillableAmountsNotFillable = (signedOrders: SignedOrderWithFillableAmounts[]) => {
signedOrders.forEach(order => {
expect(order.fillableMakerAssetAmount).to.bignumber.eq(constants.ZERO_AMOUNT);
expect(order.fillableTakerAssetAmount).to.bignumber.eq(constants.ZERO_AMOUNT);
expect(order.fillableTakerFeeAmount).to.bignumber.eq(constants.ZERO_AMOUNT);
});
};
// tslint:disable: no-unused-expression
// tslint:disable: custom-no-magic-numbers
describe('OrderStateUtils', () => {
let erc20MakerTokenContract: ERC20TokenContract;
let erc20TakerTokenContract: ERC20TokenContract;
let exchangeContract: ExchangeContract;
let userAddresses: string[];
let coinbaseAddress: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
let makerTokenAddress: string;
let takerTokenAddress: string;
let makerAssetData: string;
let takerAssetData: string;
let orderFactory: OrderFactory;
let contractAddresses: ContractAddresses;
let orderStateUtils: OrderStateUtils;
let expiredOpenSignedOrder: SignedOrder;
let invalidSignatureOpenSignedOrder: SignedOrder;
let fullyFillableOpenSignedOrder: SignedOrder;
let partiallyFilledOpenSignedOrderFeeless: SignedOrder;
let partiallyFilledOpenSignedOrderFeeInTakerAsset: SignedOrder;
let partiallyFilledOpenSignedOrderFeeInMakerAsset: SignedOrder;
let filledOpenSignedOrder: SignedOrder;
const chainId = TESTRPC_CHAIN_ID;
const fillableAmount = new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI);
const partialFillAmount = new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI);
const takerFeeAmount = new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI);
before(async () => {
contractAddresses = await migrateOnceAsync(provider);
await blockchainLifecycle.startAsync();
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
[coinbaseAddress, takerAddress, makerAddress, feeRecipient] = userAddresses;
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
erc20MakerTokenContract = new ERC20TokenContract(makerTokenAddress, provider);
erc20TakerTokenContract = new ERC20TokenContract(takerTokenAddress, provider);
exchangeContract = new ExchangeContract(contractAddresses.exchange, provider);
[makerAssetData, takerAssetData] = [
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
];
// Configure order defaults
const defaultOrderParams = {
...devConstants.STATIC_ORDER_PARAMS,
makerAddress,
takerAddress: constants.NULL_ADDRESS,
makerAssetData,
takerAssetData,
makerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
takerFeeAssetData: constants.NULL_ERC20_ASSET_DATA,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
feeRecipientAddress: feeRecipient,
exchangeAddress: contractAddresses.exchange,
chainId,
};
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
expiredOpenSignedOrder = await orderFactory.newSignedOrderAsync({
expirationTimeSeconds: new BigNumber(await getLatestBlockTimestampAsync()).minus(10),
});
invalidSignatureOpenSignedOrder = await orderFactory.newSignedOrderAsync({
takerAddress,
});
invalidSignatureOpenSignedOrder.signature = expiredOpenSignedOrder.signature;
fullyFillableOpenSignedOrder = await orderFactory.newSignedOrderAsync({
takerAssetAmount: fillableAmount,
makerAssetAmount: fillableAmount,
});
// give double fillableAmount to maker and taker as buffer
await erc20MakerTokenContract
.transfer(makerAddress, fillableAmount.multipliedBy(4))
.sendTransactionAsync({ from: coinbaseAddress });
await erc20TakerTokenContract
.transfer(takerAddress, fillableAmount.multipliedBy(4))
.sendTransactionAsync({ from: coinbaseAddress });
await erc20MakerTokenContract
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
.sendTransactionAsync({ from: makerAddress });
await erc20MakerTokenContract
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
.sendTransactionAsync({ from: takerAddress });
await erc20TakerTokenContract
.approve(contractAddresses.erc20Proxy, UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
.sendTransactionAsync({ from: takerAddress });
partiallyFilledOpenSignedOrderFeeless = await orderFactory.newSignedOrderAsync({
takerAssetAmount: fillableAmount,
makerAssetAmount: fillableAmount,
});
await exchangeContract
.fillOrKillOrder(
partiallyFilledOpenSignedOrderFeeless,
partialFillAmount,
partiallyFilledOpenSignedOrderFeeless.signature,
)
.sendTransactionAsync({
from: takerAddress,
gasPrice: GAS_PRICE,
gas: 4000000,
value: PROTOCOL_FEE_PER_FILL,
});
partiallyFilledOpenSignedOrderFeeInTakerAsset = await orderFactory.newSignedOrderAsync({
takerAssetAmount: fillableAmount,
makerAssetAmount: fillableAmount,
takerFee: takerFeeAmount,
takerFeeAssetData: takerAssetData,
});
await exchangeContract
.fillOrKillOrder(
partiallyFilledOpenSignedOrderFeeInTakerAsset,
partialFillAmount,
partiallyFilledOpenSignedOrderFeeInTakerAsset.signature,
)
.sendTransactionAsync({
from: takerAddress,
gasPrice: GAS_PRICE,
gas: 4000000,
value: PROTOCOL_FEE_PER_FILL,
});
partiallyFilledOpenSignedOrderFeeInMakerAsset = await orderFactory.newSignedOrderAsync({
takerAssetAmount: fillableAmount,
makerAssetAmount: fillableAmount,
takerFee: takerFeeAmount,
takerFeeAssetData: makerAssetData,
});
await exchangeContract
.fillOrKillOrder(
partiallyFilledOpenSignedOrderFeeInMakerAsset,
partialFillAmount,
partiallyFilledOpenSignedOrderFeeInMakerAsset.signature,
)
.sendTransactionAsync({
from: takerAddress,
gasPrice: GAS_PRICE,
gas: 4000000,
value: PROTOCOL_FEE_PER_FILL,
});
filledOpenSignedOrder = await orderFactory.newSignedOrderAsync({
takerAssetAmount: fillableAmount,
makerAssetAmount: fillableAmount,
});
await exchangeContract
.fillOrKillOrder(filledOpenSignedOrder, fillableAmount, filledOpenSignedOrder.signature)
.sendTransactionAsync({
from: takerAddress,
gasPrice: GAS_PRICE,
gas: 4000000,
value: PROTOCOL_FEE_PER_FILL,
});
orderStateUtils = new OrderStateUtils(new DevUtilsContract(contractAddresses.devUtils, provider));
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#getSignedOrdersWithFillableAmountsAsync', () => {
it('should 0 fillableTakerAssetAmount for expired orders', async () => {
const orders = [expiredOpenSignedOrder];
const resultOrders = await orderStateUtils.getSignedOrdersWithFillableAmountsAsync(orders);
isSignedOrdersWithFillableAmountsNotFillable(resultOrders);
});
it('should filter out invalid signature orders', async () => {
const orders = [invalidSignatureOpenSignedOrder];
const resultOrders = await orderStateUtils.getSignedOrdersWithFillableAmountsAsync(orders);
isSignedOrdersWithFillableAmountsNotFillable(resultOrders);
});
it('should return 0 fillableTakerAssetAmount for fully filled orders', async () => {
const orders = [filledOpenSignedOrder];
const resultOrders = await orderStateUtils.getSignedOrdersWithFillableAmountsAsync(orders);
isSignedOrdersWithFillableAmountsNotFillable(resultOrders);
});
it('should provide correct pruned signed orders for fully fillable orders', async () => {
const orders = [fullyFillableOpenSignedOrder];
const resultOrders = await orderStateUtils.getSignedOrdersWithFillableAmountsAsync(orders);
const order = resultOrders[0];
expect(order.fillableMakerAssetAmount).to.bignumber.equal(fillableAmount);
expect(order.fillableTakerAssetAmount).to.bignumber.equal(fillableAmount);
});
it('should provide correct pruned signed orders for partially fillable orders', async () => {
const orders = [
partiallyFilledOpenSignedOrderFeeless,
partiallyFilledOpenSignedOrderFeeInTakerAsset,
partiallyFilledOpenSignedOrderFeeInMakerAsset,
];
const resultOrders = await orderStateUtils.getSignedOrdersWithFillableAmountsAsync(orders);
expect(resultOrders[0].fillableMakerAssetAmount).to.bignumber.equal(
fillableAmount.minus(partialFillAmount),
);
expect(resultOrders[0].fillableTakerAssetAmount).to.bignumber.equal(
fillableAmount.minus(partialFillAmount),
);
expect(resultOrders[1].fillableMakerAssetAmount).to.bignumber.equal(
fillableAmount.minus(partialFillAmount),
);
expect(resultOrders[1].fillableTakerAssetAmount).to.bignumber.equal(
fillableAmount.minus(partialFillAmount),
);
expect(resultOrders[1].fillableTakerFeeAmount).to.bignumber.equal(
new BigNumber(1.6).multipliedBy(ONE_ETH_IN_WEI),
);
expect(resultOrders[2].fillableMakerAssetAmount).to.bignumber.equal(
fillableAmount.minus(partialFillAmount),
);
expect(resultOrders[2].fillableTakerAssetAmount).to.bignumber.equal(
fillableAmount.minus(partialFillAmount),
);
expect(resultOrders[2].fillableTakerFeeAmount).to.bignumber.equal(
new BigNumber(1.6).multipliedBy(ONE_ETH_IN_WEI),
);
});
});
});