Split 0x.js into contract-wrappers, order-watcher but keep 0x.js as a unifying library with the same interface
This commit is contained in:
@@ -41,8 +41,8 @@ describe('ZeroEx library', () => {
|
||||
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined();
|
||||
expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined();
|
||||
|
||||
// Check that all nested web3 wrapper instances return the updated provider
|
||||
const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getProvider();
|
||||
// Check that all nested zeroExContract/web3Wrapper instances return the updated provider
|
||||
const nestedWeb3WrapperProvider = (zeroEx as any)._contractWrappers.getProvider();
|
||||
expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number');
|
||||
const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getProvider();
|
||||
expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number');
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import {
|
||||
ApprovalContractEventArgs,
|
||||
BlockParamLiteral,
|
||||
BlockRange,
|
||||
DecodedLogEvent,
|
||||
DepositContractEventArgs,
|
||||
EtherTokenEvents,
|
||||
Token,
|
||||
TransferContractEventArgs,
|
||||
WithdrawalContractEventArgs,
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
} from '../src';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
// Since the address depositing/withdrawing ETH/WETH also needs to pay gas costs for the transaction,
|
||||
// a small amount of ETH will be used to pay this gas cost. We therefore check that the difference between
|
||||
// the expected balance and actual balance (given the amount of ETH deposited), only deviates by the amount
|
||||
// required to pay gas costs.
|
||||
const MAX_REASONABLE_GAS_COST_IN_WEI = 62517;
|
||||
|
||||
describe('EtherTokenWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
let userAddresses: string[];
|
||||
let addressWithETH: string;
|
||||
let wethContractAddress: string;
|
||||
let depositWeiAmount: BigNumber;
|
||||
let decimalPlaces: number;
|
||||
let addressWithoutFunds: string;
|
||||
const gasPrice = new BigNumber(1);
|
||||
const zeroExConfig = {
|
||||
gasPrice,
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const transferAmount = new BigNumber(42);
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
const depositAmount = new BigNumber(42);
|
||||
const withdrawalAmount = new BigNumber(42);
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, zeroExConfig);
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
addressWithETH = userAddresses[0];
|
||||
wethContractAddress = zeroEx.etherToken.getContractAddressIfExists() as string;
|
||||
depositWeiAmount = Web3Wrapper.toWei(new BigNumber(5));
|
||||
decimalPlaces = 7;
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#getContractAddressIfExists', async () => {
|
||||
it('should return contract address if connected to a known network', () => {
|
||||
const contractAddressIfExists = zeroEx.etherToken.getContractAddressIfExists();
|
||||
expect(contractAddressIfExists).to.not.be.undefined();
|
||||
});
|
||||
it('should throw if connected to a private network and contract addresses are not specified', () => {
|
||||
const UNKNOWN_NETWORK_NETWORK_ID = 10;
|
||||
expect(
|
||||
() =>
|
||||
new ZeroEx(provider, {
|
||||
networkId: UNKNOWN_NETWORK_NETWORK_ID,
|
||||
} as any),
|
||||
).to.throw();
|
||||
});
|
||||
});
|
||||
describe('#depositAsync', () => {
|
||||
it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => {
|
||||
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
expect(preETHBalance).to.be.bignumber.gt(0);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(0);
|
||||
|
||||
const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
|
||||
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount);
|
||||
const remainingETHInWei = preETHBalance.minus(depositWeiAmount);
|
||||
const gasCost = remainingETHInWei.minus(postETHBalanceInWei);
|
||||
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
|
||||
});
|
||||
it('should throw if user has insufficient ETH balance for deposit', async () => {
|
||||
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
|
||||
const extraETHBalance = Web3Wrapper.toWei(new BigNumber(5));
|
||||
const overETHBalanceinWei = preETHBalance.add(extraETHBalance);
|
||||
|
||||
return expect(
|
||||
zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
|
||||
});
|
||||
});
|
||||
describe('#withdrawAsync', () => {
|
||||
it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => {
|
||||
const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
|
||||
await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
|
||||
|
||||
const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount);
|
||||
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
let gasCost = expectedPreETHBalance.minus(preETHBalance);
|
||||
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
|
||||
|
||||
const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
|
||||
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0);
|
||||
const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces);
|
||||
gasCost = expectedETHBalance.minus(postETHBalance);
|
||||
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
|
||||
});
|
||||
it('should throw if user has insufficient WETH balance for withdrawal', async () => {
|
||||
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(0);
|
||||
|
||||
const overWETHBalance = preWETHBalance.add(999999999);
|
||||
|
||||
return expect(
|
||||
zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
|
||||
});
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
let etherTokenAddress: string;
|
||||
before(() => {
|
||||
const tokenUtils = new TokenUtils(tokens);
|
||||
const etherToken = tokenUtils.getWethTokenOrThrow();
|
||||
etherTokenAddress = etherToken.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.etherToken.unsubscribeAll();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribe` callback,
|
||||
// we do need both. A hack is to make the top-level async fn w/ a done callback and then
|
||||
// wrap the rest of the test in an async block
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
expect(logEvent.log.logIndex).to.be.equal(0);
|
||||
expect(logEvent.log.transactionIndex).to.be.equal(0);
|
||||
expect(logEvent.log.blockNumber).to.be.a('number');
|
||||
const args = logEvent.log.args;
|
||||
expect(args._from).to.be.equal(addressWithETH);
|
||||
expect(args._to).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(transferAmount);
|
||||
},
|
||||
);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
|
||||
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback);
|
||||
await zeroEx.token.transferAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
transferAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._spender).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(allowanceAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
allowanceAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<DepositContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._value).to.be.bignumber.equal(depositAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._value).to.be.bignumber.equal(depositAmount);
|
||||
},
|
||||
);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
|
||||
zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Withdrawal,
|
||||
indexFilterValues,
|
||||
callback,
|
||||
);
|
||||
await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackNeverToBeCalled,
|
||||
);
|
||||
const callbackToBeCalled = reportNodeCallbackErrors(done)();
|
||||
zeroEx.setProvider(provider, constants.TESTRPC_NETWORK_ID);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
|
||||
zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackToBeCalled,
|
||||
);
|
||||
await zeroEx.token.transferAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
transferAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
|
||||
const subscriptionToken = zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackNeverToBeCalled,
|
||||
);
|
||||
zeroEx.etherToken.unsubscribe(subscriptionToken);
|
||||
await zeroEx.token.transferAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
transferAmount,
|
||||
);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
describe('#getLogsAsync', () => {
|
||||
let etherTokenAddress: string;
|
||||
let tokenTransferProxyAddress: string;
|
||||
const blockRange: BlockRange = {
|
||||
fromBlock: 0,
|
||||
toBlock: BlockParamLiteral.Latest,
|
||||
};
|
||||
let txHash: string;
|
||||
before(() => {
|
||||
addressWithETH = userAddresses[0];
|
||||
const tokenUtils = new TokenUtils(tokens);
|
||||
const etherToken = tokenUtils.getWethTokenOrThrow();
|
||||
etherTokenAddress = etherToken.address;
|
||||
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
|
||||
});
|
||||
it('should get logs with decoded args emitted by Approval', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = EtherTokenEvents.Approval;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
|
||||
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should get logs with decoded args emitted by Deposit', async () => {
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
|
||||
const eventName = EtherTokenEvents.Deposit;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._value).to.be.bignumber.equal(depositAmount);
|
||||
});
|
||||
it('should only get the logs with the correct event name', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const differentEventName = EtherTokenEvents.Transfer;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync(
|
||||
etherTokenAddress,
|
||||
differentEventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(0);
|
||||
});
|
||||
it('should only get the logs with the correct indexed fields', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = EtherTokenEvents.Approval;
|
||||
const indexFilterValues = {
|
||||
_owner: addressWithETH,
|
||||
};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
import { web3Factory } from '@0xproject/dev-utils';
|
||||
import { LogEntry } from '@0xproject/types';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Sinon from 'sinon';
|
||||
|
||||
import { LogEvent } from '../src';
|
||||
import { EventWatcher } from '../src/order_watcher/event_watcher';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { provider } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('EventWatcher', () => {
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
let eventWatcher: EventWatcher;
|
||||
let web3Wrapper: Web3Wrapper;
|
||||
const logA: LogEntry = {
|
||||
address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: [],
|
||||
transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
const logB: LogEntry = {
|
||||
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
|
||||
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
const logC: LogEntry = {
|
||||
address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
|
||||
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
before(async () => {
|
||||
const pollingIntervalMs = 10;
|
||||
web3Wrapper = new Web3Wrapper(provider);
|
||||
eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs);
|
||||
});
|
||||
afterEach(() => {
|
||||
// clean up any stubs after the test has completed
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
eventWatcher.unsubscribe();
|
||||
});
|
||||
it('correctly emits initial log events', (done: DoneCallback) => {
|
||||
const logs: LogEntry[] = [logA, logB];
|
||||
const expectedLogEvents = [
|
||||
{
|
||||
removed: false,
|
||||
...logA,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logB,
|
||||
},
|
||||
];
|
||||
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
|
||||
getLogsStub.onCall(0).returns(logs);
|
||||
stubs.push(getLogsStub);
|
||||
const expectedToBeCalledOnce = false;
|
||||
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
|
||||
const expectedLogEvent = expectedLogEvents.shift();
|
||||
expect(event).to.be.deep.equal(expectedLogEvent);
|
||||
if (_.isEmpty(expectedLogEvents)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
eventWatcher.subscribe(callback);
|
||||
});
|
||||
it('correctly computes the difference and emits only changes', (done: DoneCallback) => {
|
||||
const initialLogs: LogEntry[] = [logA, logB];
|
||||
const changedLogs: LogEntry[] = [logA, logC];
|
||||
const expectedLogEvents = [
|
||||
{
|
||||
removed: false,
|
||||
...logA,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logB,
|
||||
},
|
||||
{
|
||||
removed: true,
|
||||
...logB,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logC,
|
||||
},
|
||||
];
|
||||
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
|
||||
getLogsStub.onCall(0).returns(initialLogs);
|
||||
getLogsStub.onCall(1).returns(changedLogs);
|
||||
stubs.push(getLogsStub);
|
||||
const expectedToBeCalledOnce = false;
|
||||
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
|
||||
const expectedLogEvent = expectedLogEvents.shift();
|
||||
expect(event).to.be.deep.equal(expectedLogEvent);
|
||||
if (_.isEmpty(expectedLogEvents)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
eventWatcher.subscribe(callback);
|
||||
});
|
||||
});
|
||||
@@ -1,117 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { BlockParamLiteral } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { ExchangeContractErrs, Token, ZeroEx } from '../src';
|
||||
import { TradeSide, TransferType } from '../src/types';
|
||||
import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simulator';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('ExchangeTransferSimulator', () => {
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const zeroEx = new ZeroEx(provider, config);
|
||||
const transferAmount = new BigNumber(5);
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let coinbase: string;
|
||||
let sender: string;
|
||||
let recipient: string;
|
||||
let exampleTokenAddress: string;
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let txHash: string;
|
||||
before(async () => {
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[coinbase, sender, recipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
exampleTokenAddress = tokens[0].address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#transferFromAsync', () => {
|
||||
beforeEach(() => {
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
|
||||
});
|
||||
it("throws if the user doesn't have enough allowance", async () => {
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
|
||||
});
|
||||
it("throws if the user doesn't have enough balance", async () => {
|
||||
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
});
|
||||
it('updates balances and proxyAllowance after transfer', async () => {
|
||||
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(0);
|
||||
});
|
||||
it("doesn't update proxyAllowance after transfer if unlimited", async () => {
|
||||
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,195 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Sinon from 'sinon';
|
||||
|
||||
import { ZeroEx } from '../src/0x';
|
||||
import { ExpirationWatcher } from '../src/order_watcher/expiration_watcher';
|
||||
import { DoneCallback, Token } from '../src/types';
|
||||
import { utils } from '../src/utils/utils';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { reportNoErrorCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('ExpirationWatcher', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let tokenUtils: TokenUtils;
|
||||
let tokens: Token[];
|
||||
let userAddresses: string[];
|
||||
let zrxTokenAddress: string;
|
||||
let fillScenarios: FillScenarios;
|
||||
let exchangeContractAddress: string;
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let coinbase: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
let currentUnixTimestampSec: BigNumber;
|
||||
let timer: Sinon.SinonFakeTimers;
|
||||
let expirationWatcher: ExpirationWatcher;
|
||||
before(async () => {
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
exchangeContractAddress = zeroEx.exchange.getContractAddress();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
const sinonTimerConfig = { shouldAdvanceTime: true } as any;
|
||||
// This constructor has incorrect types
|
||||
timer = Sinon.useFakeTimers(sinonTimerConfig);
|
||||
currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||
expirationWatcher = new ExpirationWatcher();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
timer.restore();
|
||||
expirationWatcher.unsubscribe();
|
||||
});
|
||||
it('correctly emits events when order expires', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const orderLifetimeSec = 60;
|
||||
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => {
|
||||
expect(hash).to.be.equal(orderHash);
|
||||
expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
timer.tick(orderLifetimeSec * 1000);
|
||||
})().catch(done);
|
||||
});
|
||||
it("doesn't emit events before order expires", (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const orderLifetimeSec = 60;
|
||||
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => {
|
||||
done(new Error('Emitted expiration went before the order actually expired'));
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
const notEnoughTime = orderLifetimeSec - 1;
|
||||
timer.tick(notEnoughTime * 1000);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
it('emits events in correct order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const order1Lifetime = 60;
|
||||
const order2Lifetime = 120;
|
||||
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
|
||||
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
|
||||
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
order1ExpirationUnixTimestampSec,
|
||||
);
|
||||
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
order2ExpirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1);
|
||||
const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2);
|
||||
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000));
|
||||
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000));
|
||||
const expirationOrder = [orderHash1, orderHash2];
|
||||
const expectToBeCalledOnce = false;
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => {
|
||||
const orderHash = expirationOrder.shift();
|
||||
expect(hash).to.be.equal(orderHash);
|
||||
if (_.isEmpty(expirationOrder)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
timer.tick(order2Lifetime * 1000);
|
||||
})().catch(done);
|
||||
});
|
||||
it('emits events in correct order when expirations are equal', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const order1Lifetime = 60;
|
||||
const order2Lifetime = 60;
|
||||
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
|
||||
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
|
||||
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
order1ExpirationUnixTimestampSec,
|
||||
);
|
||||
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
order2ExpirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1);
|
||||
const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2);
|
||||
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000));
|
||||
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000));
|
||||
const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1];
|
||||
const expectToBeCalledOnce = false;
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => {
|
||||
const orderHash = expirationOrder.shift();
|
||||
expect(hash).to.be.equal(orderHash);
|
||||
if (_.isEmpty(expirationOrder)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
timer.tick(order2Lifetime * 1000);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
@@ -1,558 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
|
||||
import {
|
||||
ExchangeContractErrs,
|
||||
OrderState,
|
||||
OrderStateInvalid,
|
||||
OrderStateValid,
|
||||
SignedOrder,
|
||||
Token,
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
} from '../src';
|
||||
import { OrderStateWatcher } from '../src/order_watcher/order_state_watcher';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
const TIMEOUT_MS = 150;
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('OrderStateWatcher', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let fillScenarios: FillScenarios;
|
||||
let userAddresses: string[];
|
||||
let zrxTokenAddress: string;
|
||||
let exchangeContractAddress: string;
|
||||
let makerToken: Token;
|
||||
let takerToken: Token;
|
||||
let maker: string;
|
||||
let taker: string;
|
||||
let signedOrder: SignedOrder;
|
||||
let orderStateWatcher: OrderStateWatcher;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const decimals = constants.ZRX_DECIMALS;
|
||||
const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
orderStateWatcher = zeroEx.createOrderStateWatcher();
|
||||
exchangeContractAddress = zeroEx.exchange.getContractAddress();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[, maker, taker] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
await fillScenarios.initTokenBalancesAsync();
|
||||
[makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#removeOrder', async () => {
|
||||
it('should successfully remove existing order', async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
expect((orderStateWatcher as any)._orderByOrderHash).to.include({
|
||||
[orderHash]: signedOrder,
|
||||
});
|
||||
let dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes;
|
||||
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash);
|
||||
orderStateWatcher.removeOrder(orderHash);
|
||||
expect((orderStateWatcher as any)._orderByOrderHash).to.not.include({
|
||||
[orderHash]: signedOrder,
|
||||
});
|
||||
dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes;
|
||||
expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined();
|
||||
});
|
||||
it('should no-op when removing a non-existing order', async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
const nonExistentOrderHash = `0x${orderHash
|
||||
.substr(2)
|
||||
.split('')
|
||||
.reverse()
|
||||
.join('')}`;
|
||||
orderStateWatcher.removeOrder(nonExistentOrderHash);
|
||||
});
|
||||
});
|
||||
describe('#subscribe', async () => {
|
||||
afterEach(async () => {
|
||||
orderStateWatcher.unsubscribe();
|
||||
});
|
||||
it('should fail when trying to subscribe twice', async () => {
|
||||
orderStateWatcher.subscribe(_.noop);
|
||||
expect(() => orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent);
|
||||
});
|
||||
});
|
||||
describe('tests with cleanup', async () => {
|
||||
afterEach(async () => {
|
||||
orderStateWatcher.unsubscribe();
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.removeOrder(orderHash);
|
||||
});
|
||||
it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = 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);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0));
|
||||
})().catch(done);
|
||||
});
|
||||
it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
throw new Error('OrderState callback fired for irrelevant order');
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
const notTheMaker = userAddresses[0];
|
||||
const anyRecipient = taker;
|
||||
const transferAmount = new BigNumber(2);
|
||||
await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount);
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, TIMEOUT_MS);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = 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);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
const anyRecipient = taker;
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = 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);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
fillableAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
const fillAmountInBaseUnits = new BigNumber(2);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = 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.sub(fillAmountInBaseUnits);
|
||||
const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits);
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFillable,
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFillable,
|
||||
);
|
||||
expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
fillAmountInBaseUnits,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18);
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
taker,
|
||||
);
|
||||
const callback = reportNodeCallbackErrors(done)();
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0));
|
||||
})().catch(done);
|
||||
});
|
||||
describe('remainingFillable(M|T)akerTokenAmount', () => {
|
||||
it('should calculate correct remaining fillable', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals);
|
||||
const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals);
|
||||
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
);
|
||||
const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = 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.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals),
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals),
|
||||
);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
fillAmountInBaseUnits,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
changedMakerApprovalAmount,
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
changedMakerApprovalAmount,
|
||||
);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
|
||||
const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
|
||||
const transferAmount = makerBalance.sub(remainingAmount);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingAmount,
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingAmount,
|
||||
);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
|
||||
const feeRecipient = taker;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
|
||||
const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
|
||||
const transferTokenAmount = makerFee.sub(remainingTokenAmount);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingTokenAmount,
|
||||
);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
|
||||
const feeRecipient = taker;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
|
||||
const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
|
||||
|
||||
const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
|
||||
const transferTokenAmount = makerFee.sub(remainingTokenAmount);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFeeAmount,
|
||||
);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount);
|
||||
await zeroEx.token.transferAsync(
|
||||
makerToken.address,
|
||||
maker,
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
transferTokenAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
const feeRecipient = taker;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
fillableAmount,
|
||||
);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(
|
||||
makerToken.address,
|
||||
maker,
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals),
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = 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);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const remainingFillableAmountInBaseUnits = new BigNumber(100);
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = 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);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(
|
||||
signedOrder,
|
||||
fillableAmount.minus(remainingFillableAmountInBaseUnits),
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const cancelAmountInBaseUnits = new BigNumber(2);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = 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.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits);
|
||||
});
|
||||
orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
}); // tslint:disable:max-file-line-count
|
||||
@@ -1,507 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { OrderError } from '@0xproject/order-utils';
|
||||
import { BlockParamLiteral } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as Sinon from 'sinon';
|
||||
|
||||
import { ExchangeContractErrs, SignedOrder, Token, ZeroEx, ZeroExError } from '../src';
|
||||
import { TradeSide, TransferType } from '../src/types';
|
||||
import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simulator';
|
||||
import { OrderValidationUtils } from '../src/utils/order_validation_utils';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('OrderValidation', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let exchangeContractAddress: string;
|
||||
let zrxTokenAddress: string;
|
||||
let fillScenarios: FillScenarios;
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let coinbase: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const fillTakerAmount = new BigNumber(5);
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
exchangeContractAddress = zeroEx.exchange.getContractAddress();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('validateOrderFillableOrThrowAsync', () => {
|
||||
it('should succeed if the order is fillable', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
|
||||
});
|
||||
it('should succeed if the maker is buying ZRX and has no ZRX balance', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
zrxTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
const zrxMakerBalance = await zeroEx.token.getBalanceAsync(zrxTokenAddress, makerAddress);
|
||||
await zeroEx.token.transferAsync(zrxTokenAddress, makerAddress, takerAddress, zrxMakerBalance);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
|
||||
});
|
||||
it('should succeed if the maker is buying ZRX and has no ZRX balance and there is no specified taker', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
zrxTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
const zrxMakerBalance = await zeroEx.token.getBalanceAsync(zrxTokenAddress, makerAddress);
|
||||
await zeroEx.token.transferAsync(zrxTokenAddress, makerAddress, takerAddress, zrxMakerBalance);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
|
||||
});
|
||||
it('should succeed if the order is asymmetric and fillable', async () => {
|
||||
const makerFillableAmount = fillableAmount;
|
||||
const takerFillableAmount = fillableAmount.minus(4);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
|
||||
ExchangeContractErrs.OrderRemainingFillAmountZero,
|
||||
);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationInPast,
|
||||
);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
|
||||
ExchangeContractErrs.OrderFillExpired,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw when the fill amount is zero', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
const zeroFillAmount = new BigNumber(0);
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, zeroFillAmount, takerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should throw when the signature is invalid', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
// 27 <--> 28
|
||||
signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27;
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress),
|
||||
).to.be.rejectedWith(OrderError.InvalidSignature);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
it('should throw when sender is not a taker', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
const nonTakerAddress = userAddresses[6];
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationInPast,
|
||||
);
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
|
||||
});
|
||||
it('should throw when there a rounding error would have occurred', async () => {
|
||||
const makerAmount = new BigNumber(3);
|
||||
const takerAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerAmount,
|
||||
takerAmount,
|
||||
);
|
||||
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder,
|
||||
fillTakerAmountThatCausesRoundingError,
|
||||
takerAddress,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
|
||||
});
|
||||
});
|
||||
describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
const tooLargeFillAmount = new BigNumber(7);
|
||||
const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
|
||||
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
|
||||
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
|
||||
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
signedOrder,
|
||||
tooLargeFillAmount,
|
||||
takerAddress,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
});
|
||||
});
|
||||
describe('validateCancelOrderAndThrowIfInvalidAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
const cancelAmount = new BigNumber(3);
|
||||
beforeEach(async () => {
|
||||
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
});
|
||||
it('should throw when cancel amount is zero', async () => {
|
||||
const zeroCancelAmount = new BigNumber(0);
|
||||
return expect(
|
||||
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationInPast,
|
||||
);
|
||||
return expect(
|
||||
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
|
||||
});
|
||||
it('should throw when order is already cancelled or filled', async () => {
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(
|
||||
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
|
||||
});
|
||||
});
|
||||
describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => {
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let transferFromAsync: Sinon.SinonSpy;
|
||||
const bigNumberMatch = (expected: BigNumber) => {
|
||||
return Sinon.match((value: BigNumber) => value.eq(expected));
|
||||
};
|
||||
beforeEach('create exchangeTransferSimulator', async () => {
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
|
||||
transferFromAsync = Sinon.spy();
|
||||
exchangeTransferSimulator.transferFromAsync = transferFromAsync as any;
|
||||
});
|
||||
it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
fillableAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(0)
|
||||
.calledWith(
|
||||
makerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(1)
|
||||
.calledWith(
|
||||
takerTokenAddress,
|
||||
takerAddress,
|
||||
makerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(2)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
makerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(makerFee),
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(3)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
takerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(takerFee),
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
});
|
||||
it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
fillableAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(0)
|
||||
.calledWith(
|
||||
makerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(1)
|
||||
.calledWith(
|
||||
takerTokenAddress,
|
||||
takerAddress,
|
||||
makerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(2)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
makerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(makerFee),
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(3)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
takerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(takerFee),
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
});
|
||||
it('should correctly round the fillMakerTokenAmount', async () => {
|
||||
const makerTokenAmount = new BigNumber(3);
|
||||
const takerTokenAmount = new BigNumber(1);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerTokenAmount,
|
||||
takerTokenAmount,
|
||||
);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
takerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
const makerFillAmount = transferFromAsync.getCall(0).args[3];
|
||||
expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount);
|
||||
});
|
||||
it('should correctly round the makerFeeAmount', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(4);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
);
|
||||
const fillTakerTokenAmount = fillableAmount.div(2).round(0);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
fillTakerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
const makerPartialFee = makerFee.div(2);
|
||||
const takerPartialFee = takerFee.div(2);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
const partialMakerFee = transferFromAsync.getCall(2).args[3];
|
||||
expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee);
|
||||
const partialTakerFee = transferFromAsync.getCall(3).args[3];
|
||||
expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee);
|
||||
});
|
||||
});
|
||||
}); // tslint:disable-line:max-file-line-count
|
||||
@@ -1,234 +0,0 @@
|
||||
import { ECSignature, SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { ZeroEx } from '../src/0x';
|
||||
import { RemainingFillableCalculator } from '../src/order_watcher/remaining_fillable_calculator';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('RemainingFillableCalculator', () => {
|
||||
let calculator: RemainingFillableCalculator;
|
||||
let signedOrder: SignedOrder;
|
||||
let transferrableMakerTokenAmount: BigNumber;
|
||||
let transferrableMakerFeeTokenAmount: BigNumber;
|
||||
let remainingMakerTokenAmount: BigNumber;
|
||||
let makerAmount: BigNumber;
|
||||
let takerAmount: BigNumber;
|
||||
let makerFeeAmount: BigNumber;
|
||||
let isMakerTokenZRX: boolean;
|
||||
const makerToken: string = '0x1';
|
||||
const takerToken: string = '0x2';
|
||||
const decimals: number = 4;
|
||||
const zero: BigNumber = new BigNumber(0);
|
||||
const zeroAddress = '0x0';
|
||||
const signature: ECSignature = { v: 27, r: '', s: '' };
|
||||
beforeEach(async () => {
|
||||
[makerAmount, takerAmount, makerFeeAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals),
|
||||
];
|
||||
[transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
|
||||
];
|
||||
});
|
||||
function buildSignedOrder(): SignedOrder {
|
||||
return {
|
||||
ecSignature: signature,
|
||||
exchangeContractAddress: zeroAddress,
|
||||
feeRecipient: zeroAddress,
|
||||
maker: zeroAddress,
|
||||
taker: zeroAddress,
|
||||
makerFee: makerFeeAmount,
|
||||
takerFee: zero,
|
||||
makerTokenAmount: makerAmount,
|
||||
takerTokenAmount: takerAmount,
|
||||
makerTokenAddress: makerToken,
|
||||
takerTokenAddress: takerToken,
|
||||
salt: zero,
|
||||
expirationUnixTimestampSec: zero,
|
||||
};
|
||||
}
|
||||
describe('Maker token is NOT ZRX', () => {
|
||||
before(async () => {
|
||||
isMakerTokenZRX = false;
|
||||
});
|
||||
it('calculates the correct amount when unfilled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the correct amount when partially filled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the amount to be 0 when all fee funds are transferred', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
transferrableMakerFeeTokenAmount = zero;
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
|
||||
});
|
||||
it('calculates the correct amount when balance is less than remaining fillable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
|
||||
});
|
||||
describe('Order to Fee Ratio is < 1', () => {
|
||||
beforeEach(async () => {
|
||||
[makerAmount, takerAmount, makerFeeAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
|
||||
];
|
||||
});
|
||||
it('calculates the correct amount when funds unavailable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
|
||||
});
|
||||
});
|
||||
describe('Ratio is not evenly divisble', () => {
|
||||
beforeEach(async () => {
|
||||
[makerAmount, takerAmount, makerFeeAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
|
||||
];
|
||||
});
|
||||
it('calculates the correct amount when funds unavailable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
|
||||
expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true();
|
||||
expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0));
|
||||
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee);
|
||||
const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio);
|
||||
expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Maker Token is ZRX', () => {
|
||||
before(async () => {
|
||||
isMakerTokenZRX = true;
|
||||
});
|
||||
it('calculates the correct amount when unfilled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount);
|
||||
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the correct amount when partially filled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the amount to be 0 when all fee funds are transferred', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
transferrableMakerTokenAmount = zero;
|
||||
transferrableMakerFeeTokenAmount = zero;
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
|
||||
});
|
||||
it('calculates the correct amount when balance is less than remaining fillable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
|
||||
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
|
||||
|
||||
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee);
|
||||
const expectedFillableAmount = new BigNumber(450980);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
|
||||
const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio);
|
||||
const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio);
|
||||
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount);
|
||||
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount);
|
||||
expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount);
|
||||
expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Sinon from 'sinon';
|
||||
|
||||
import { ApprovalContractEventArgs, DecodedLogEvent, Token, TokenEvents, ZeroEx } from '../src';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { assertNodeCallbackError } from './utils/report_callback_errors';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('SubscriptionTest', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let coinbase: string;
|
||||
let addressWithoutFunds: string;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
coinbase = userAddresses[0];
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
let tokenAddress: string;
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.token.unsubscribeAll();
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
});
|
||||
it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const errMsg = 'Error fetching block';
|
||||
const callback = assertNodeCallbackError(done, errMsg);
|
||||
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))];
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const errMsg = 'Error fetching logs';
|
||||
const callback = assertNodeCallbackError(done, errMsg);
|
||||
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))];
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop;
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))];
|
||||
zeroEx.token.unsubscribeAll();
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,128 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
|
||||
import { Token, ZeroEx } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7;
|
||||
|
||||
describe('TokenRegistryWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
const tokenAddressBySymbol: { [symbol: string]: string } = {};
|
||||
const tokenAddressByName: { [symbol: string]: string } = {};
|
||||
const tokenBySymbol: { [symbol: string]: Token } = {};
|
||||
const tokenByName: { [symbol: string]: Token } = {};
|
||||
const registeredSymbol = 'ZRX';
|
||||
const registeredName = '0x Protocol Token';
|
||||
const unregisteredSymbol = 'MAL';
|
||||
const unregisteredName = 'Malicious Token';
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
_.map(tokens, token => {
|
||||
tokenAddressBySymbol[token.symbol] = token.address;
|
||||
tokenAddressByName[token.name] = token.address;
|
||||
tokenBySymbol[token.symbol] = token;
|
||||
tokenByName[token.name] = token;
|
||||
});
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#getTokensAsync', () => {
|
||||
it('should return all the tokens added to the tokenRegistry during the migration', async () => {
|
||||
expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
|
||||
|
||||
const schemaValidator = new SchemaValidator();
|
||||
_.each(tokens, token => {
|
||||
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#getTokenAddressesAsync', () => {
|
||||
it('should return all the token addresses added to the tokenRegistry during the migration', async () => {
|
||||
const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync();
|
||||
expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
|
||||
|
||||
const schemaValidator = new SchemaValidator();
|
||||
_.each(tokenAddresses, tokenAddress => {
|
||||
const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#getTokenAddressBySymbol', () => {
|
||||
it('should return correct address for a token in the registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol);
|
||||
expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol);
|
||||
expect(tokenAddress).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenAddressByName', () => {
|
||||
it('should return correct address for a token in the registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName);
|
||||
expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName);
|
||||
expect(tokenAddress).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenBySymbol', () => {
|
||||
it('should return correct token for a token in the registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol);
|
||||
expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol);
|
||||
expect(token).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenByName', () => {
|
||||
it('should return correct token for a token in the registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName);
|
||||
expect(token).to.be.deep.equal(tokenByName[registeredName]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName);
|
||||
expect(token).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenIfExistsAsync', () => {
|
||||
it('should return the token added to the tokenRegistry during the migration', async () => {
|
||||
const aToken = tokens[0];
|
||||
|
||||
const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address);
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
});
|
||||
it('should return return undefined when passed a token address not in the tokenRegistry', async () => {
|
||||
const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||
const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress);
|
||||
expect(tokenIfExists).to.be.undefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { ZeroEx } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { provider } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('TokenTransferProxyWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
});
|
||||
describe('#isAuthorizedAsync', () => {
|
||||
it('should return false if the address is not authorized', async () => {
|
||||
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS);
|
||||
expect(isAuthorized).to.be.false();
|
||||
});
|
||||
});
|
||||
describe('#getAuthorizedAddressesAsync', () => {
|
||||
it('should return the list of authorized addresses', async () => {
|
||||
const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync();
|
||||
for (const authorizedAddress of authorizedAddresses) {
|
||||
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress);
|
||||
expect(isAuthorized).to.be.true();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,528 +0,0 @@
|
||||
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
|
||||
import { EmptyWalletSubprovider } from '@0xproject/subproviders';
|
||||
import { Provider } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
import Web3ProviderEngine = require('web3-provider-engine');
|
||||
|
||||
import {
|
||||
ApprovalContractEventArgs,
|
||||
BlockParamLiteral,
|
||||
BlockRange,
|
||||
DecodedLogEvent,
|
||||
Token,
|
||||
TokenEvents,
|
||||
TransferContractEventArgs,
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
} from '../src';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('TokenWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let coinbase: string;
|
||||
let addressWithoutFunds: string;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
zeroEx = new ZeroEx(provider, config);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
coinbase = userAddresses[0];
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#transferAsync', () => {
|
||||
let token: Token;
|
||||
let transferAmount: BigNumber;
|
||||
before(() => {
|
||||
token = tokens[0];
|
||||
transferAmount = new BigNumber(42);
|
||||
});
|
||||
it('should successfully transfer tokens', async () => {
|
||||
const fromAddress = coinbase;
|
||||
const toAddress = addressWithoutFunds;
|
||||
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
expect(preBalance).to.be.bignumber.equal(0);
|
||||
await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
return expect(postBalance).to.be.bignumber.equal(transferAmount);
|
||||
});
|
||||
it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => {
|
||||
const fromAddress = addressWithoutFunds;
|
||||
const toAddress = coinbase;
|
||||
return expect(
|
||||
zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
const fromAddress = coinbase;
|
||||
const toAddress = coinbase;
|
||||
return expect(
|
||||
zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
describe('#transferFromAsync', () => {
|
||||
let token: Token;
|
||||
let toAddress: string;
|
||||
let senderAddress: string;
|
||||
before(async () => {
|
||||
token = tokens[0];
|
||||
toAddress = addressWithoutFunds;
|
||||
senderAddress = userAddresses[2];
|
||||
});
|
||||
it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => {
|
||||
const fromAddress = coinbase;
|
||||
const transferAmount = new BigNumber(42);
|
||||
|
||||
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
|
||||
expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount);
|
||||
|
||||
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress);
|
||||
expect(fromAddressAllowance).to.be.bignumber.equal(0);
|
||||
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
});
|
||||
it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => {
|
||||
const fromAddress = coinbase;
|
||||
const transferAmount = new BigNumber(42);
|
||||
|
||||
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
});
|
||||
it('should fail to transfer tokens if fromAddress has insufficient balance', async () => {
|
||||
const fromAddress = addressWithoutFunds;
|
||||
const transferAmount = new BigNumber(42);
|
||||
|
||||
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
|
||||
expect(fromAddressBalance).to.be.bignumber.equal(0);
|
||||
|
||||
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
|
||||
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(
|
||||
token.address,
|
||||
fromAddress,
|
||||
senderAddress,
|
||||
);
|
||||
expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount);
|
||||
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
});
|
||||
it('should successfully transfer tokens', async () => {
|
||||
const fromAddress = coinbase;
|
||||
|
||||
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
expect(preBalance).to.be.bignumber.equal(0);
|
||||
|
||||
const transferAmount = new BigNumber(42);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
|
||||
|
||||
await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount);
|
||||
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
return expect(postBalance).to.be.bignumber.equal(transferAmount);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const fromAddress = coinbase;
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(
|
||||
nonExistentTokenAddress,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
senderAddress,
|
||||
new BigNumber(42),
|
||||
),
|
||||
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
describe('#getBalanceAsync', () => {
|
||||
describe('With provider with accounts', () => {
|
||||
it('should return the balance for an existing ERC20 token', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('1000000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
const ownerAddress = coinbase;
|
||||
return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith(
|
||||
ZeroExError.TokenContractDoesNotExist,
|
||||
);
|
||||
});
|
||||
it('should return a balance of 0 for a non-existent owner address', async () => {
|
||||
const token = tokens[0];
|
||||
const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593';
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner);
|
||||
const expectedBalance = new BigNumber(0);
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
});
|
||||
describe('With provider without accounts', () => {
|
||||
let zeroExWithoutAccounts: ZeroEx;
|
||||
before(async () => {
|
||||
const hasAddresses = false;
|
||||
const emptyWalletProvider = addEmptyWalletSubprovider(provider);
|
||||
zeroExWithoutAccounts = new ZeroEx(emptyWalletProvider, config);
|
||||
});
|
||||
it('should return balance even when called with provider instance without addresses', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('1000000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#setAllowanceAsync', () => {
|
||||
it("should set the spender's allowance", async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync(
|
||||
token.address,
|
||||
ownerAddress,
|
||||
spenderAddress,
|
||||
);
|
||||
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
|
||||
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowanceAfterAllowanceSet = amountInBaseUnits;
|
||||
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
|
||||
});
|
||||
});
|
||||
describe('#setUnlimitedAllowanceAsync', () => {
|
||||
it("should set the unlimited spender's allowance", async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => {
|
||||
const transferAmount = new BigNumber(5);
|
||||
const zrx = tokenUtils.getProtocolTokenOrThrow();
|
||||
const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses;
|
||||
await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount);
|
||||
await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance);
|
||||
|
||||
const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
|
||||
const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
|
||||
userWithUnlimitedAllowance,
|
||||
);
|
||||
|
||||
await zeroEx.token.transferFromAsync(
|
||||
zrx.address,
|
||||
coinbase,
|
||||
userWithNormalAllowance,
|
||||
userWithNormalAllowance,
|
||||
transferAmount,
|
||||
);
|
||||
await zeroEx.token.transferFromAsync(
|
||||
zrx.address,
|
||||
coinbase,
|
||||
userWithUnlimitedAllowance,
|
||||
userWithUnlimitedAllowance,
|
||||
transferAmount,
|
||||
);
|
||||
|
||||
const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
|
||||
const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
|
||||
userWithUnlimitedAllowance,
|
||||
);
|
||||
|
||||
const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance);
|
||||
const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance);
|
||||
|
||||
// In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger.
|
||||
// This needs to be investigated in ethereumjs-vm. This test is essentially a repro.
|
||||
// TODO: Make this test pass with inverted assertion.
|
||||
expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber());
|
||||
});
|
||||
});
|
||||
describe('#getAllowanceAsync', () => {
|
||||
describe('With provider with accounts', () => {
|
||||
it('should get the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
it('should return 0 if no allowance set yet', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowance = new BigNumber(0);
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
});
|
||||
describe('With provider without accounts', () => {
|
||||
let zeroExWithoutAccounts: ZeroEx;
|
||||
before(async () => {
|
||||
const hasAddresses = false;
|
||||
const emptyWalletProvider = addEmptyWalletSubprovider(provider);
|
||||
zeroExWithoutAccounts = new ZeroEx(emptyWalletProvider, config);
|
||||
});
|
||||
it('should get the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync(
|
||||
token.address,
|
||||
ownerAddress,
|
||||
spenderAddress,
|
||||
);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#getProxyAllowanceAsync', () => {
|
||||
it('should get the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits);
|
||||
|
||||
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
});
|
||||
describe('#setProxyAllowanceAsync', () => {
|
||||
it('should set the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
|
||||
const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
|
||||
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
|
||||
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits);
|
||||
|
||||
const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
|
||||
const expectedAllowanceAfterAllowanceSet = amountInBaseUnits;
|
||||
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
|
||||
});
|
||||
});
|
||||
describe('#setUnlimitedProxyAllowanceAsync', () => {
|
||||
it('should set the unlimited proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
|
||||
await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress);
|
||||
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
|
||||
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
let tokenAddress: string;
|
||||
const transferAmount = new BigNumber(42);
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.token.unsubscribeAll();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribe` callback,
|
||||
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
|
||||
// wrap the rest of the test in an async block
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
expect(logEvent.log.logIndex).to.be.equal(0);
|
||||
expect(logEvent.log.transactionIndex).to.be.equal(0);
|
||||
expect(logEvent.log.blockNumber).to.be.a('number');
|
||||
const args = logEvent.log.args;
|
||||
expect(args._from).to.be.equal(coinbase);
|
||||
expect(args._to).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(transferAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(allowanceAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
|
||||
const callbackToBeCalled = reportNodeCallbackErrors(done)();
|
||||
zeroEx.setProvider(provider, constants.TESTRPC_NETWORK_ID);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
const subscriptionToken = zeroEx.token.subscribe(
|
||||
tokenAddress,
|
||||
TokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackNeverToBeCalled,
|
||||
);
|
||||
zeroEx.token.unsubscribe(subscriptionToken);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
describe('#getLogsAsync', () => {
|
||||
let tokenAddress: string;
|
||||
let tokenTransferProxyAddress: string;
|
||||
const blockRange: BlockRange = {
|
||||
fromBlock: 0,
|
||||
toBlock: BlockParamLiteral.Latest,
|
||||
};
|
||||
let txHash: string;
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
|
||||
});
|
||||
it('should get logs with decoded args emitted by Approval', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = TokenEvents.Approval;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
|
||||
tokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
|
||||
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should only get the logs with the correct event name', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const differentEventName = TokenEvents.Transfer;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.token.getLogsAsync(
|
||||
tokenAddress,
|
||||
differentEventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(0);
|
||||
});
|
||||
it('should only get the logs with the correct indexed fields', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = TokenEvents.Approval;
|
||||
const indexFilterValues = {
|
||||
_owner: coinbase,
|
||||
};
|
||||
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
|
||||
tokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
||||
|
||||
function addEmptyWalletSubprovider(p: Provider): Provider {
|
||||
const providerEngine = new Web3ProviderEngine();
|
||||
providerEngine.addProvider(new EmptyWalletSubprovider());
|
||||
const currentSubproviders = (p as any)._providers;
|
||||
for (const subprovider of currentSubproviders) {
|
||||
providerEngine.addProvider(subprovider);
|
||||
}
|
||||
providerEngine.start();
|
||||
return providerEngine;
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
import { orderFactory } from '@0xproject/order-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
import { SignedOrder, Token, ZeroEx } from '../../src';
|
||||
import { artifacts } from '../../src/artifacts';
|
||||
import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
|
||||
|
||||
import { constants } from './constants';
|
||||
|
||||
const INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS = new BigNumber(100);
|
||||
|
||||
export class FillScenarios {
|
||||
private _zeroEx: ZeroEx;
|
||||
private _userAddresses: string[];
|
||||
private _tokens: Token[];
|
||||
private _coinbase: string;
|
||||
private _zrxTokenAddress: string;
|
||||
private _exchangeContractAddress: string;
|
||||
constructor(
|
||||
zeroEx: ZeroEx,
|
||||
userAddresses: string[],
|
||||
tokens: Token[],
|
||||
zrxTokenAddress: string,
|
||||
exchangeContractAddress: string,
|
||||
) {
|
||||
this._zeroEx = zeroEx;
|
||||
this._userAddresses = userAddresses;
|
||||
this._tokens = tokens;
|
||||
this._coinbase = userAddresses[0];
|
||||
this._zrxTokenAddress = zrxTokenAddress;
|
||||
this._exchangeContractAddress = exchangeContractAddress;
|
||||
}
|
||||
public async initTokenBalancesAsync() {
|
||||
const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper;
|
||||
for (const token of this._tokens) {
|
||||
if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') {
|
||||
const defaults = {};
|
||||
const dummyToken = new DummyTokenContract(
|
||||
artifacts.DummyTokenArtifact.abi,
|
||||
token.address,
|
||||
web3Wrapper.getProvider(),
|
||||
web3Wrapper.getContractDefaults(),
|
||||
);
|
||||
const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals);
|
||||
const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, {
|
||||
from: this._coinbase,
|
||||
});
|
||||
await this._zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
public async createFillableSignedOrderAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
fillableAmount: BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
return this.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
fillableAmount,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
}
|
||||
public async createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
fillableAmount: BigNumber,
|
||||
feeRecepient: string,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
fillableAmount,
|
||||
feeRecepient,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
}
|
||||
public async createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
makerFillableAmount: BigNumber,
|
||||
takerFillableAmount: BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
const makerFee = new BigNumber(0);
|
||||
const takerFee = new BigNumber(0);
|
||||
const feeRecepient = constants.NULL_ADDRESS;
|
||||
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
feeRecepient,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
}
|
||||
public async createPartiallyFilledSignedOrderAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
takerAddress: string,
|
||||
fillableAmount: BigNumber,
|
||||
partialFillAmount: BigNumber,
|
||||
) {
|
||||
const [makerAddress] = this._userAddresses;
|
||||
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
fillableAmount,
|
||||
);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = false;
|
||||
await this._zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
partialFillAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
);
|
||||
return signedOrder;
|
||||
}
|
||||
private async _createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
makerFillableAmount: BigNumber,
|
||||
takerFillableAmount: BigNumber,
|
||||
feeRecepient: string,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
await Promise.all([
|
||||
this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount),
|
||||
this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount),
|
||||
]);
|
||||
await Promise.all([
|
||||
this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee),
|
||||
this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee),
|
||||
]);
|
||||
|
||||
const signedOrder = await orderFactory.createSignedOrderAsync(
|
||||
this._zeroEx.getProvider(),
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerFillableAmount,
|
||||
makerTokenAddress,
|
||||
takerFillableAmount,
|
||||
takerTokenAddress,
|
||||
this._exchangeContractAddress,
|
||||
feeRecepient,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
return signedOrder;
|
||||
}
|
||||
private async _increaseBalanceAndAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
address: string,
|
||||
amount: BigNumber,
|
||||
): Promise<void> {
|
||||
if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) {
|
||||
return; // noop
|
||||
}
|
||||
await Promise.all([
|
||||
this._increaseBalanceAsync(tokenAddress, address, amount),
|
||||
this._increaseAllowanceAsync(tokenAddress, address, amount),
|
||||
]);
|
||||
}
|
||||
private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
||||
await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount);
|
||||
}
|
||||
private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
||||
const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address);
|
||||
const newMakerAllowance = oldMakerAllowance.plus(amount);
|
||||
await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DoneCallback } from '../../src/types';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
export const reportNoErrorCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => {
|
||||
return <T>(f?: (value: T) => void) => {
|
||||
const wrapped = (value: T) => {
|
||||
if (_.isUndefined(f)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
f(value);
|
||||
if (expectToBeCalledOnce) {
|
||||
done();
|
||||
}
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
};
|
||||
|
||||
export const reportNodeCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => {
|
||||
return <T>(f?: (value: T) => void) => {
|
||||
const wrapped = (error: Error | null, value: T | undefined) => {
|
||||
if (!_.isNull(error)) {
|
||||
done(error);
|
||||
} else {
|
||||
if (_.isUndefined(f)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
f(value as T);
|
||||
if (expectToBeCalledOnce) {
|
||||
done();
|
||||
}
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
};
|
||||
|
||||
export const assertNodeCallbackError = (done: DoneCallback, errMsg: string) => {
|
||||
const wrapped = <T>(error: Error | null, value: T | undefined) => {
|
||||
if (_.isNull(error)) {
|
||||
done(new Error('Expected callback to receive an error'));
|
||||
} else {
|
||||
try {
|
||||
expect(error.message).to.be.equal(errMsg);
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Token } from '@0xproject/types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { InternalZeroExError, Token } from '../../src/types';
|
||||
import { InternalZeroExError } from '../../src/types';
|
||||
|
||||
const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
|
||||
const WETH_TOKEN_SYMBOL = 'WETH';
|
||||
|
||||
Reference in New Issue
Block a user