* switch @0x/contract-wrappers to generated wrappers - remove TransactionEncoder - move TokenUtils to @0x/dev-utils - detailed changes in #2040
939 lines
48 KiB
TypeScript
939 lines
48 KiB
TypeScript
// tslint:disable:no-unnecessary-type-assertion
|
|
import { ContractAddresses, ContractWrappers } from '@0x/contract-wrappers';
|
|
import { BlockchainLifecycle, callbackErrorReporter, tokenUtils } from '@0x/dev-utils';
|
|
import { FillScenarios } from '@0x/fill-scenarios';
|
|
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
|
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
|
|
import {
|
|
DoneCallback,
|
|
ExchangeContractErrs,
|
|
OrderState,
|
|
OrderStateInvalid,
|
|
OrderStateValid,
|
|
SignedOrder,
|
|
} from '@0x/types';
|
|
import { BigNumber } from '@0x/utils';
|
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
import * as chai from 'chai';
|
|
import * as _ from 'lodash';
|
|
import 'mocha';
|
|
|
|
import {
|
|
DependentOrderHashesTracker,
|
|
OrderHashesByERC20ByMakerAddress,
|
|
} from '../src/order_watcher/dependent_order_hashes_tracker';
|
|
import { OrderWatcher } from '../src/order_watcher/order_watcher';
|
|
import { OrderWatcherError } from '../src/types';
|
|
|
|
import { chaiSetup } from './utils/chai_setup';
|
|
import { constants } from './utils/constants';
|
|
import { migrateOnceAsync } from './utils/migrate';
|
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
|
|
|
const TIMEOUT_MS = 150;
|
|
|
|
chaiSetup.configure();
|
|
const expect = chai.expect;
|
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
|
|
describe('OrderWatcher', () => {
|
|
let contractWrappers: ContractWrappers;
|
|
let fillScenarios: FillScenarios;
|
|
let userAddresses: string[];
|
|
let zrxTokenAddress: string;
|
|
let makerAssetData: string;
|
|
let takerAssetData: string;
|
|
let makerTokenAddress: string;
|
|
let takerTokenAddress: string;
|
|
let makerAddress: string;
|
|
let takerAddress: string;
|
|
let coinbase: string;
|
|
let feeRecipient: string;
|
|
let signedOrder: SignedOrder;
|
|
let orderWatcher: OrderWatcher;
|
|
let contractAddresses: ContractAddresses;
|
|
const decimals = constants.ZRX_DECIMALS;
|
|
const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals);
|
|
before(async () => {
|
|
contractAddresses = await migrateOnceAsync();
|
|
await blockchainLifecycle.startAsync();
|
|
const networkId = constants.TESTRPC_NETWORK_ID;
|
|
const config = {
|
|
networkId,
|
|
contractAddresses,
|
|
};
|
|
contractWrappers = new ContractWrappers(provider, config);
|
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
|
zrxTokenAddress = contractAddresses.zrxToken;
|
|
fillScenarios = new FillScenarios(
|
|
provider,
|
|
userAddresses,
|
|
zrxTokenAddress,
|
|
contractAddresses.exchange,
|
|
contractAddresses.erc20Proxy,
|
|
contractAddresses.erc721Proxy,
|
|
);
|
|
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
|
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
|
[makerAssetData, takerAssetData] = [
|
|
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
|
|
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
|
|
];
|
|
const orderWatcherConfig = {};
|
|
orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig);
|
|
});
|
|
after(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
beforeEach(async () => {
|
|
await blockchainLifecycle.startAsync();
|
|
});
|
|
afterEach(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
describe('DependentOrderHashesTracker', async () => {
|
|
let makerErc721TokenAddress: string;
|
|
[makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses();
|
|
it('should handle lookups on unknown addresses', async () => {
|
|
// Regression test
|
|
// ApprovalForAll events on a token from an untracked address could cause
|
|
// nested lookups on undefined object
|
|
// #1550
|
|
const dependentOrderHashesTracker = (orderWatcher as any)
|
|
._dependentOrderHashesTracker as DependentOrderHashesTracker;
|
|
dependentOrderHashesTracker.getDependentOrderHashesByERC721ByMaker(takerAddress, makerErc721TokenAddress);
|
|
});
|
|
});
|
|
describe('#removeOrder', async () => {
|
|
it('should successfully remove existing order', async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
expect((orderWatcher as any)._orderByOrderHash).to.include({
|
|
[orderHash]: signedOrder,
|
|
});
|
|
const dependentOrderHashesTracker = (orderWatcher as any)
|
|
._dependentOrderHashesTracker as DependentOrderHashesTracker;
|
|
let orderHashesByERC20ByMakerAddress: OrderHashesByERC20ByMakerAddress = (dependentOrderHashesTracker as any)
|
|
._orderHashesByERC20ByMakerAddress;
|
|
expect(orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][makerTokenAddress]).to.have.keys(
|
|
orderHash,
|
|
);
|
|
orderWatcher.removeOrder(orderHash);
|
|
expect((orderWatcher as any)._orderByOrderHash).to.not.include({
|
|
[orderHash]: signedOrder,
|
|
});
|
|
orderHashesByERC20ByMakerAddress = (dependentOrderHashesTracker as any)._orderHashesByERC20ByMakerAddress;
|
|
expect(orderHashesByERC20ByMakerAddress[signedOrder.makerAddress]).to.be.undefined();
|
|
});
|
|
it('should no-op when removing a non-existing order', async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
const nonExistentOrderHash = `0x${orderHash
|
|
.substr(2)
|
|
.split('')
|
|
.reverse()
|
|
.join('')}`;
|
|
orderWatcher.removeOrder(nonExistentOrderHash);
|
|
});
|
|
});
|
|
describe('#subscribe', async () => {
|
|
afterEach(async () => {
|
|
orderWatcher.unsubscribe();
|
|
});
|
|
it('should fail when trying to subscribe twice', async () => {
|
|
orderWatcher.subscribe(_.noop.bind(_));
|
|
expect(() => orderWatcher.subscribe(_.noop.bind(_))).to.throw(OrderWatcherError.SubscriptionAlreadyPresent);
|
|
});
|
|
});
|
|
describe('#getStats', async () => {
|
|
it('orderCount should increment and decrement with order additions and removals', async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
expect(orderWatcher.getStats().orderCount).to.be.eq(0);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
expect(orderWatcher.getStats().orderCount).to.be.eq(1);
|
|
orderWatcher.removeOrder(orderHash);
|
|
expect(orderWatcher.getStats().orderCount).to.be.eq(0);
|
|
});
|
|
});
|
|
describe('tests with cleanup', async () => {
|
|
beforeEach(async () => {
|
|
await blockchainLifecycle.startAsync();
|
|
});
|
|
afterEach(async () => {
|
|
orderWatcher.unsubscribe();
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
orderWatcher.removeOrder(orderHash);
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
it('should emit orderStateInvalid when makerAddress allowance set to 0 for watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
new BigNumber(0),
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((_orderState: OrderState) => {
|
|
throw new Error('OrderState callback fired for irrelevant order');
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
const notTheMaker = userAddresses[0];
|
|
const anyRecipient = takerAddress;
|
|
const transferAmount = new BigNumber(2);
|
|
await contractWrappers.erc20Token.transferAsync(
|
|
makerTokenAddress,
|
|
notTheMaker,
|
|
anyRecipient,
|
|
transferAmount,
|
|
);
|
|
setTimeout(() => {
|
|
done();
|
|
}, TIMEOUT_MS);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when makerAddress moves balance backing watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
const anyRecipient = takerAddress;
|
|
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
await contractWrappers.erc20Token.transferAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
anyRecipient,
|
|
makerBalance,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
|
|
await contractWrappers.exchange.fillOrderAsync(signedOrder, fillableAmount, takerAddress);
|
|
})().catch(done);
|
|
});
|
|
it('should include transactionHash in emitted orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
let transactionHash: string;
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.transactionHash).to.be.equal(transactionHash);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
|
|
transactionHash = await contractWrappers.exchange.fillOrderAsync(
|
|
signedOrder,
|
|
fillableAmount,
|
|
takerAddress,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
|
|
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress);
|
|
const fillAmountInBaseUnits = new BigNumber(2);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.true();
|
|
const validOrderState = orderState as OrderStateValid;
|
|
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
|
const orderRelevantState = validOrderState.orderRelevantState;
|
|
const remainingMakerBalance = makerBalance.minus(fillAmountInBaseUnits);
|
|
const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits);
|
|
expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
|
|
remainingFillable,
|
|
);
|
|
expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
|
|
remainingFillable,
|
|
);
|
|
expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress);
|
|
})().catch(done);
|
|
});
|
|
it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
|
|
(async () => {
|
|
const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
|
|
const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
|
|
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerFee,
|
|
takerFee,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
takerAddress,
|
|
);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)();
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
zrxTokenAddress,
|
|
makerAddress,
|
|
new BigNumber(0),
|
|
);
|
|
})().catch(done);
|
|
});
|
|
describe('remainingFillable(M|T)akerTokenAmount', () => {
|
|
it('should calculate correct remaining fillable', (done: DoneCallback) => {
|
|
(async () => {
|
|
const takerFillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), decimals);
|
|
const makerFillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(20), decimals);
|
|
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
makerFillableAmount,
|
|
takerFillableAmount,
|
|
);
|
|
const fillAmountInBaseUnits = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.true();
|
|
const validOrderState = orderState as OrderStateValid;
|
|
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
|
const orderRelevantState = validOrderState.orderRelevantState;
|
|
expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
|
|
Web3Wrapper.toBaseUnitAmount(new BigNumber(16), decimals),
|
|
);
|
|
expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
|
|
Web3Wrapper.toBaseUnitAmount(new BigNumber(8), decimals),
|
|
);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress);
|
|
})().catch(done);
|
|
});
|
|
it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
|
|
const changedMakerApprovalAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
const validOrderState = orderState as OrderStateValid;
|
|
const orderRelevantState = validOrderState.orderRelevantState;
|
|
expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
|
|
changedMakerApprovalAmount,
|
|
);
|
|
expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
|
|
changedMakerApprovalAmount,
|
|
);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
changedMakerApprovalAmount,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
|
|
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
);
|
|
|
|
const remainingAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals);
|
|
const transferAmount = makerBalance.minus(remainingAmount);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.true();
|
|
const validOrderState = orderState as OrderStateValid;
|
|
const orderRelevantState = validOrderState.orderRelevantState;
|
|
expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
|
|
remainingAmount,
|
|
);
|
|
expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal(
|
|
remainingAmount,
|
|
);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.transferAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
constants.NULL_ADDRESS,
|
|
transferAmount,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => {
|
|
(async () => {
|
|
const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
|
|
const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals);
|
|
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerFee,
|
|
takerFee,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
feeRecipient,
|
|
);
|
|
|
|
const remainingFeeAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals);
|
|
|
|
const remainingTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), decimals);
|
|
const transferTokenAmount = makerFee.minus(remainingTokenAmount);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
const validOrderState = orderState as OrderStateValid;
|
|
const orderRelevantState = validOrderState.orderRelevantState;
|
|
expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
|
|
remainingFeeAmount,
|
|
);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
zrxTokenAddress,
|
|
makerAddress,
|
|
remainingFeeAmount,
|
|
);
|
|
await contractWrappers.erc20Token.transferAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
constants.NULL_ADDRESS,
|
|
transferTokenAmount,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => {
|
|
(async () => {
|
|
const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
|
|
const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
|
|
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerFee,
|
|
takerFee,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
feeRecipient,
|
|
);
|
|
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
const validOrderState = orderState as OrderStateValid;
|
|
const orderRelevantState = validOrderState.orderRelevantState;
|
|
expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal(
|
|
fillableAmount,
|
|
);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
Web3Wrapper.toBaseUnitAmount(new BigNumber(100), decimals),
|
|
);
|
|
})().catch(done);
|
|
});
|
|
});
|
|
it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderCancelled);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.exchange.cancelOrderAsync(signedOrder);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when within rounding error range after a partial fill', (done: DoneCallback) => {
|
|
(async () => {
|
|
const fillAmountInBaseUnits = new BigNumber(2);
|
|
const makerAssetAmount = new BigNumber(1001);
|
|
const takerAssetAmount = new BigNumber(3);
|
|
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
makerAssetAmount,
|
|
takerAssetAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when makerAddress is unfunded by withdrawing WETH', (done: DoneCallback) => {
|
|
(async () => {
|
|
const etherTokenAddress = contractAddresses.etherToken;
|
|
const wethAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress);
|
|
await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, makerAddress);
|
|
const depositAmount = fillableAmount.times(2);
|
|
await contractWrappers.etherToken.depositAsync(etherTokenAddress, depositAmount, makerAddress);
|
|
// WETH for ZRX order
|
|
signedOrder = await orderFactory.createSignedOrderAsync(
|
|
web3Wrapper.getProvider(),
|
|
makerAddress,
|
|
fillableAmount,
|
|
wethAssetData,
|
|
fillableAmount,
|
|
takerAssetData,
|
|
contractAddresses.exchange,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.etherToken.withdrawAsync(
|
|
contractAddresses.etherToken,
|
|
depositAmount,
|
|
makerAddress,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
describe('erc721', () => {
|
|
let makerErc721AssetData: string;
|
|
let makerErc721TokenAddress: string;
|
|
const tokenId = new BigNumber(42);
|
|
[makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses();
|
|
makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId);
|
|
const fillableErc721Amount = new BigNumber(1);
|
|
it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerErc721AssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc721Token.setApprovalAsync(
|
|
makerErc721TokenAddress,
|
|
constants.NULL_ADDRESS,
|
|
tokenId,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when maker allowance for all set to 0 for watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerErc721AssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
await contractWrappers.erc721Token.setApprovalAsync(
|
|
makerErc721TokenAddress,
|
|
constants.NULL_ADDRESS,
|
|
tokenId,
|
|
);
|
|
let isApproved = true;
|
|
await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
|
|
makerErc721TokenAddress,
|
|
makerAddress,
|
|
isApproved,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
isApproved = false;
|
|
await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
|
|
makerErc721TokenAddress,
|
|
makerAddress,
|
|
isApproved,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when maker moves NFT backing watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerErc721AssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc721Token.transferFromAsync(
|
|
makerErc721TokenAddress,
|
|
coinbase,
|
|
makerAddress,
|
|
tokenId,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
});
|
|
describe('multiAsset', async () => {
|
|
const tokenId = new BigNumber(42);
|
|
const [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses();
|
|
const makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId);
|
|
const fillableErc721Amount = new BigNumber(1);
|
|
const [makerErc20TokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
|
const makerErc20AssetData = assetDataUtils.encodeERC20AssetData(makerErc20TokenAddress);
|
|
const fillableErc20Amount = new BigNumber(2);
|
|
const multiAssetAmounts = [fillableErc721Amount, fillableErc20Amount];
|
|
const nestedAssetData = [makerErc721AssetData, makerErc20AssetData];
|
|
const makerMultiAssetData = assetDataUtils.encodeMultiAssetData(multiAssetAmounts, nestedAssetData);
|
|
it('should emit orderStateInvalid when maker allowance of ERC721 token set to 0 for watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc721Token.setApprovalAsync(
|
|
makerErc721TokenAddress,
|
|
constants.NULL_ADDRESS,
|
|
tokenId,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when maker allowance for all of ERC721 token set to 0 for watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
await contractWrappers.erc721Token.setApprovalAsync(
|
|
makerErc721TokenAddress,
|
|
constants.NULL_ADDRESS,
|
|
tokenId,
|
|
);
|
|
let isApproved = true;
|
|
await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
|
|
makerErc721TokenAddress,
|
|
makerAddress,
|
|
isApproved,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
isApproved = false;
|
|
await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
|
|
makerErc721TokenAddress,
|
|
makerAddress,
|
|
isApproved,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when maker moves ERC721 backing watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc721Token.transferFromAsync(
|
|
makerErc721TokenAddress,
|
|
coinbase,
|
|
makerAddress,
|
|
tokenId,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when maker allowance of ERC20 token set to 0 for watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableErc721Amount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
await contractWrappers.erc20Token.setProxyAllowanceAsync(
|
|
makerErc20TokenAddress,
|
|
makerAddress,
|
|
new BigNumber(0),
|
|
);
|
|
})().catch(done);
|
|
});
|
|
it('should not emit an orderState event when irrelevant ERC20 Transfer event received', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((_orderState: OrderState) => {
|
|
throw new Error('OrderState callback fired for irrelevant order');
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
const notTheMaker = userAddresses[0];
|
|
const anyRecipient = takerAddress;
|
|
const transferAmount = new BigNumber(2);
|
|
await contractWrappers.erc20Token.transferAsync(
|
|
makerTokenAddress,
|
|
notTheMaker,
|
|
anyRecipient,
|
|
transferAmount,
|
|
);
|
|
setTimeout(() => {
|
|
done();
|
|
}, TIMEOUT_MS);
|
|
})().catch(done);
|
|
});
|
|
it('should emit orderStateInvalid when makerAddress moves ERC20 balance backing watched order', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
const anyRecipient = takerAddress;
|
|
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
);
|
|
await contractWrappers.erc20Token.transferAsync(
|
|
makerTokenAddress,
|
|
makerAddress,
|
|
anyRecipient,
|
|
makerBalance,
|
|
);
|
|
})().catch(done);
|
|
});
|
|
// TODO(abandeali1): The following test will fail until the MAP has been deployed and activated.
|
|
it.skip('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
|
|
(async () => {
|
|
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerMultiAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
await orderWatcher.addOrderAsync(signedOrder);
|
|
|
|
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
|
expect(orderState.isValid).to.be.false();
|
|
const invalidOrderState = orderState as OrderStateInvalid;
|
|
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
|
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
|
});
|
|
orderWatcher.subscribe(callback);
|
|
|
|
await contractWrappers.exchange.fillOrderAsync(signedOrder, fillableAmount, takerAddress);
|
|
})().catch(done);
|
|
});
|
|
});
|
|
});
|
|
}); // tslint:disable:max-file-line-count
|