aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/0x.js_test.ts104
-rw-r--r--test/exchange_wrapper.ts92
-rw-r--r--test/exchange_wrapper_test.ts328
-rw-r--r--test/utils/blockchain_lifecycle.ts2
-rw-r--r--test/utils/fill_scenarios.ts84
-rw-r--r--test/utils/order_factory.ts43
-rw-r--r--test/utils/token_utils.ts24
7 files changed, 532 insertions, 145 deletions
diff --git a/test/0x.js_test.ts b/test/0x.js_test.ts
index 5d23d7094..efc703ea1 100644
--- a/test/0x.js_test.ts
+++ b/test/0x.js_test.ts
@@ -3,13 +3,15 @@ import * as chai from 'chai';
import 'mocha';
import * as BigNumber from 'bignumber.js';
import ChaiBigNumber = require('chai-bignumber');
+import * as dirtyChai from 'dirty-chai';
import * as Sinon from 'sinon';
import {ZeroEx} from '../src/0x.js';
import {constants} from './utils/constants';
import {web3Factory} from './utils/web3_factory';
+import {Order} from '../src/types';
-// Use BigNumber chai add-on
chai.use(ChaiBigNumber());
+chai.use(dirtyChai);
const expect = chai.expect;
describe('ZeroEx library', () => {
@@ -20,8 +22,8 @@ describe('ZeroEx library', () => {
// Instantiate the contract instances with the current provider
await (zeroEx.exchange as any).getExchangeContractAsync();
await (zeroEx.tokenRegistry as any).getTokenRegistryContractAsync();
- expect((zeroEx.exchange as any).exchangeContractIfExists).to.not.be.undefined;
- expect((zeroEx.tokenRegistry as any).tokenRegistryContractIfExists).to.not.be.undefined;
+ expect((zeroEx.exchange as any).exchangeContractIfExists).to.not.be.undefined();
+ expect((zeroEx.tokenRegistry as any).tokenRegistryContractIfExists).to.not.be.undefined();
const newProvider = web3Factory.getRpcProvider();
// Add property to newProvider so that we can differentiate it from old provider
@@ -29,8 +31,8 @@ describe('ZeroEx library', () => {
zeroEx.setProvider(newProvider);
// Check that contractInstances with old provider are removed after provider update
- expect((zeroEx.exchange as any).exchangeContractIfExists).to.be.undefined;
- expect((zeroEx.tokenRegistry as any).tokenRegistryContractIfExists).to.be.undefined;
+ expect((zeroEx.exchange as any).exchangeContractIfExists).to.be.undefined();
+ expect((zeroEx.tokenRegistry as any).tokenRegistryContractIfExists).to.be.undefined();
// Check that all nested web3 instances return the updated provider
const nestedWeb3WrapperProvider = (zeroEx as any).web3Wrapper.getCurrentProvider();
@@ -41,43 +43,6 @@ describe('ZeroEx library', () => {
expect((tokenRegistryWeb3WrapperProvider as any).zeroExTestId).to.be.a('number');
});
});
- describe('#getOrderHash', () => {
- const expectedOrderHash = '0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7';
- it('defaults takerAddress to NULL address', () => {
- const orderHash = ZeroEx.getOrderHashHex(
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- '',
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- );
- expect(orderHash).to.be.equal(expectedOrderHash);
- });
- it('calculates the order hash', () => {
- const orderHash = ZeroEx.getOrderHashHex(
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- constants.NULL_ADDRESS,
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- new BigNumber(0),
- );
- expect(orderHash).to.be.equal(expectedOrderHash);
- });
- });
describe('#isValidSignature', () => {
// This test data was borrowed from the JSON RPC documentation
// Source: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
@@ -127,47 +92,47 @@ describe('ZeroEx library', () => {
});
it('should return false if the data doesn\'t pertain to the signature & address', () => {
const isValid = ZeroEx.isValidSignature('0x0', signature, address);
- expect(isValid).to.be.false;
+ expect(isValid).to.be.false();
});
it('should return false if the address doesn\'t pertain to the signature & data', () => {
const validUnrelatedAddress = '0x8b0292B11a196601eD2ce54B665CaFEca0347D42';
const isValid = ZeroEx.isValidSignature(data, signature, validUnrelatedAddress);
- expect(isValid).to.be.false;
+ expect(isValid).to.be.false();
});
it('should return false if the signature doesn\'t pertain to the data & address', () => {
const wrongSignature = _.assign({}, signature, {v: 28});
const isValid = ZeroEx.isValidSignature(data, wrongSignature, address);
- expect(isValid).to.be.false;
+ expect(isValid).to.be.false();
});
it('should return true if the signature does pertain to the data & address', () => {
const isValid = ZeroEx.isValidSignature(data, signature, address);
- expect(isValid).to.be.true;
+ expect(isValid).to.be.true();
});
});
describe('#generateSalt', () => {
it('generates different salts', () => {
const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt());
- expect(equal).to.be.false;
+ expect(equal).to.be.false();
});
it('generates salt in range [0..2^256)', () => {
const salt = ZeroEx.generatePseudoRandomSalt();
- expect(salt.greaterThanOrEqualTo(0)).to.be.true;
+ expect(salt.greaterThanOrEqualTo(0)).to.be.true();
const twoPow256 = new BigNumber(2).pow(256);
- expect(salt.lessThan(twoPow256)).to.be.true;
+ expect(salt.lessThan(twoPow256)).to.be.true();
});
});
describe('#isValidOrderHash', () => {
it('returns false if the value is not a hex string', () => {
const isValid = ZeroEx.isValidOrderHash('not a hex');
- expect(isValid).to.be.false;
+ expect(isValid).to.be.false();
});
it('returns false if the length is wrong', () => {
const isValid = ZeroEx.isValidOrderHash('0xdeadbeef');
- expect(isValid).to.be.false;
+ expect(isValid).to.be.false();
});
it('returns true if order hash is correct', () => {
const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0'));
- expect(isValid).to.be.true;
+ expect(isValid).to.be.true();
});
});
describe('#toUnitAmount', () => {
@@ -188,6 +153,41 @@ describe('ZeroEx library', () => {
expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount);
});
});
+ describe('#getOrderHashAsync', () => {
+ const exchangeContractAddress = constants.NULL_ADDRESS;
+ const expectedOrderHash = '0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7';
+ const order: Order = {
+ maker: constants.NULL_ADDRESS,
+ taker: constants.NULL_ADDRESS,
+ feeRecipient: constants.NULL_ADDRESS,
+ makerTokenAddress: constants.NULL_ADDRESS,
+ takerTokenAddress: constants.NULL_ADDRESS,
+ salt: new BigNumber(0),
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ makerTokenAmount: new BigNumber(0),
+ takerTokenAmount: new BigNumber(0),
+ expirationUnixTimestampSec: new BigNumber(0),
+ };
+ let stubs: Sinon.SinonStub[] = [];
+ afterEach(() => {
+ // clean up any stubs after the test has completed
+ _.each(stubs, s => s.restore());
+ stubs = [];
+ });
+ it('calculates the order hash', async () => {
+ const web3 = web3Factory.create();
+ const zeroEx = new ZeroEx(web3);
+
+ stubs = [
+ Sinon.stub((zeroEx as any), 'getExchangeAddressAsync')
+ .returns(Promise.resolve(exchangeContractAddress)),
+ ];
+
+ const orderHash = await zeroEx.getOrderHashHexAsync(order);
+ expect(orderHash).to.be.equal(expectedOrderHash);
+ });
+ });
describe('#signOrderHashAsync', () => {
let stubs: Sinon.SinonStub[] = [];
afterEach(() => {
diff --git a/test/exchange_wrapper.ts b/test/exchange_wrapper.ts
deleted file mode 100644
index e42454089..000000000
--- a/test/exchange_wrapper.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import 'mocha';
-import * as chai from 'chai';
-import {web3Factory} from './utils/web3_factory';
-import {ZeroEx} from '../src/0x.js';
-import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
-
-const expect = chai.expect;
-const blockchainLifecycle = new BlockchainLifecycle();
-
-describe('ExchangeWrapper', () => {
- let zeroEx: ZeroEx;
- before(async () => {
- const web3 = web3Factory.create();
- zeroEx = new ZeroEx(web3);
- });
- beforeEach(async () => {
- await blockchainLifecycle.startAsync();
- });
- afterEach(async () => {
- await blockchainLifecycle.revertAsync();
- });
- describe('#isValidSignatureAsync', () => {
- // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes
- // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size.
- const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
- const signature = {
- v: 27,
- r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
- s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
- };
- const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
- describe('should throw if passed a malformed signature', () => {
- it('malformed v', async () => {
- const malformedSignature = {
- v: 34,
- r: signature.r,
- s: signature.s,
- };
- expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
- .to.be.rejected;
- });
- it('r lacks 0x prefix', () => {
- const malformedR = signature.r.replace('0x', '');
- const malformedSignature = {
- v: signature.v,
- r: malformedR,
- s: signature.s,
- };
- expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
- .to.be.rejected;
- });
- it('r is too short', () => {
- const malformedR = signature.r.substr(10);
- const malformedSignature = {
- v: signature.v,
- r: malformedR,
- s: signature.s.replace('0', 'z'),
- };
- expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
- .to.be.rejected;
- });
- it('s is not hex', () => {
- const malformedS = signature.s.replace('0', 'z');
- const malformedSignature = {
- v: signature.v,
- r: signature.r,
- s: malformedS,
- };
- expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
- .to.be.rejected;
- });
- });
- it('should return false if the data doesn\'t pertain to the signature & address', async () => {
- const isValid = await zeroEx.exchange.isValidSignatureAsync('0x0', signature, address);
- expect(isValid).to.be.false;
- });
- it('should return false if the address doesn\'t pertain to the signature & dataHex', async () => {
- const validUnrelatedAddress = '0x8b0292B11a196601eD2ce54B665CaFEca0347D42';
- const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, signature, validUnrelatedAddress);
- expect(isValid).to.be.false;
- });
- it('should return false if the signature doesn\'t pertain to the dataHex & address', async () => {
- const wrongSignature = Object.assign({}, signature, {v: 28});
- const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, wrongSignature, address);
- expect(isValid).to.be.false;
- });
- it('should return true if the signature does pertain to the dataHex & address', async () => {
- const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, signature, address);
- expect(isValid).to.be.true;
- });
- });
-});
diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts
new file mode 100644
index 000000000..6327ef004
--- /dev/null
+++ b/test/exchange_wrapper_test.ts
@@ -0,0 +1,328 @@
+import 'mocha';
+import * as _ from 'lodash';
+import * as chai from 'chai';
+import * as Web3 from 'web3';
+import * as BigNumber from 'bignumber.js';
+import * as dirtyChai from 'dirty-chai';
+import ChaiBigNumber = require('chai-bignumber');
+import promisify = require('es6-promisify');
+import {web3Factory} from './utils/web3_factory';
+import {ZeroEx} from '../src/0x.js';
+import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
+import {ExchangeContractErrs, SignedOrder, Token} from '../src/types';
+import {FillScenarios} from './utils/fill_scenarios';
+import {TokenUtils} from './utils/token_utils';
+
+chai.use(dirtyChai);
+chai.use(ChaiBigNumber());
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle();
+
+describe('ExchangeWrapper', () => {
+ let web3: Web3;
+ let zeroEx: ZeroEx;
+ let userAddresses: string[];
+ before(async () => {
+ web3 = web3Factory.create();
+ zeroEx = new ZeroEx(web3);
+ userAddresses = await promisify(web3.eth.getAccounts)();
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('#isValidSignatureAsync', () => {
+ // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes
+ // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size.
+ const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
+ const signature = {
+ v: 27,
+ r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
+ s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
+ };
+ const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
+ describe('should throw if passed a malformed signature', () => {
+ it('malformed v', async () => {
+ const malformedSignature = {
+ v: 34,
+ r: signature.r,
+ s: signature.s,
+ };
+ return expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
+ .to.be.rejected();
+ });
+ it('r lacks 0x prefix', async () => {
+ const malformedR = signature.r.replace('0x', '');
+ const malformedSignature = {
+ v: signature.v,
+ r: malformedR,
+ s: signature.s,
+ };
+ return expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
+ .to.be.rejected();
+ });
+ it('r is too short', async () => {
+ const malformedR = signature.r.substr(10);
+ const malformedSignature = {
+ v: signature.v,
+ r: malformedR,
+ s: signature.s.replace('0', 'z'),
+ };
+ return expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
+ .to.be.rejected();
+ });
+ it('s is not hex', async () => {
+ const malformedS = signature.s.replace('0', 'z');
+ const malformedSignature = {
+ v: signature.v,
+ r: signature.r,
+ s: malformedS,
+ };
+ return expect(zeroEx.exchange.isValidSignatureAsync(dataHex, malformedSignature, address))
+ .to.be.rejected();
+ });
+ });
+ it('should return false if the data doesn\'t pertain to the signature & address', async () => {
+ const isValid = await zeroEx.exchange.isValidSignatureAsync('0x0', signature, address);
+ expect(isValid).to.be.false();
+ });
+ it('should return false if the address doesn\'t pertain to the signature & dataHex', async () => {
+ const validUnrelatedAddress = '0x8b0292B11a196601eD2ce54B665CaFEca0347D42';
+ const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, signature, validUnrelatedAddress);
+ expect(isValid).to.be.false();
+ });
+ it('should return false if the signature doesn\'t pertain to the dataHex & address', async () => {
+ const wrongSignature = {...signature, v: 28};
+ const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, wrongSignature, address);
+ expect(isValid).to.be.false();
+ });
+ it('should return true if the signature does pertain to the dataHex & address', async () => {
+ const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, signature, address);
+ expect(isValid).to.be.true();
+ });
+ });
+ describe('#fillOrderAsync', () => {
+ let tokens: Token[];
+ let makerTokenAddress: string;
+ let takerTokenAddress: string;
+ let fillScenarios: FillScenarios;
+ let coinbase: string;
+ let makerAddress: string;
+ let takerAddress: string;
+ let feeRecipient: string;
+ let zrxTokenAddress: string;
+ const fillTakerAmount = new BigNumber(5);
+ const shouldCheckTransfer = false;
+ before(async () => {
+ [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
+ tokens = await zeroEx.tokenRegistry.getTokensAsync();
+ const tokenUtils = new TokenUtils(tokens);
+ const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
+ makerTokenAddress = makerToken.address;
+ takerTokenAddress = takerToken.address;
+ zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
+ fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress);
+ });
+ afterEach('reset default account', () => {
+ zeroEx.setTransactionSenderAccount(userAddresses[0]);
+ });
+ describe('failed fills', () => {
+ it('should throw when the fill amount is zero', async () => {
+ const fillableAmount = new BigNumber(5);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ const zeroFillAmount = new BigNumber(0);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, zeroFillAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO);
+ });
+ it('should throw when sender is not a taker', async () => {
+ const fillableAmount = new BigNumber(5);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER);
+ });
+ it('should throw when order is expired', async () => {
+ const expirationInPast = new BigNumber(42);
+ const fillableAmount = new BigNumber(5);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, expirationInPast,
+ );
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.ORDER_FILL_EXPIRED);
+ });
+ describe('should throw when not enough balance or allowance to fulfill the order', () => {
+ const fillableAmount = new BigNumber(5);
+ const balanceToSubtractFromMaker = new BigNumber(3);
+ const lackingAllowance = new BigNumber(3);
+ let signedOrder: SignedOrder;
+ beforeEach('create fillable signed order', async () => {
+ signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ });
+ it('should throw when taker balance is less than fill amount', async () => {
+ await zeroEx.token.transferAsync(
+ takerTokenAddress, takerAddress, coinbase, balanceToSubtractFromMaker,
+ );
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_BALANCE);
+ });
+ it('should throw when taker allowance is less than fill amount', async () => {
+ const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
+ await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress,
+ newAllowanceWhichIsLessThanFillAmount);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_ALLOWANCE);
+ });
+ it('should throw when maker balance is less than maker fill amount', async () => {
+ await zeroEx.token.transferAsync(
+ makerTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
+ );
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_BALANCE);
+ });
+ it('should throw when maker allowance is less than maker fill amount', async () => {
+ const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
+ await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress,
+ newAllowanceWhichIsLessThanFillAmount);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_ALLOWANCE);
+ });
+ });
+ 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);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmountThatCausesRoundingError, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR);
+ });
+ describe('should throw when not enough balance or allowance to pay fees', () => {
+ const fillableAmount = new BigNumber(5);
+ const makerFee = new BigNumber(2);
+ const takerFee = new BigNumber(2);
+ let signedOrder: SignedOrder;
+ beforeEach('setup', async () => {
+ signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
+ makerTokenAddress, takerTokenAddress, makerFee, takerFee,
+ makerAddress, takerAddress, fillableAmount, feeRecipient,
+ );
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ });
+ it('should throw when maker doesn\'t have enough balance to pay fees', async () => {
+ const balanceToSubtractFromMaker = new BigNumber(1);
+ await zeroEx.token.transferAsync(
+ zrxTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
+ );
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_BALANCE);
+ });
+ it('should throw when maker doesn\'t have enough allowance to pay fees', async () => {
+ const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
+ await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress,
+ newAllowanceWhichIsLessThanFees);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_ALLOWANCE);
+ });
+ it('should throw when taker doesn\'t have enough balance to pay fees', async () => {
+ const balanceToSubtractFromTaker = new BigNumber(1);
+ await zeroEx.token.transferAsync(
+ zrxTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
+ );
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_BALANCE);
+ });
+ it('should throw when taker doesn\'t have enough allowance to pay fees', async () => {
+ const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
+ await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, takerAddress,
+ newAllowanceWhichIsLessThanFees);
+ return expect(zeroEx.exchange.fillOrderAsync(
+ signedOrder, fillTakerAmount, shouldCheckTransfer,
+ )).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_ALLOWANCE);
+ });
+ });
+ });
+ describe('successful fills', () => {
+ it('should fill a valid order', async () => {
+ const fillableAmount = new BigNumber(5);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
+ .to.be.bignumber.equal(fillableAmount);
+ expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
+ .to.be.bignumber.equal(0);
+ expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
+ .to.be.bignumber.equal(0);
+ expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
+ .to.be.bignumber.equal(fillableAmount);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmount, shouldCheckTransfer);
+ expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
+ .to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
+ expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
+ .to.be.bignumber.equal(fillTakerAmount);
+ expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
+ .to.be.bignumber.equal(fillTakerAmount);
+ expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
+ .to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
+ });
+ it('should partially fill the valid order', async () => {
+ const fillableAmount = new BigNumber(5);
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ const partialFillAmount = new BigNumber(3);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ await zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldCheckTransfer);
+ expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
+ .to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
+ expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
+ .to.be.bignumber.equal(partialFillAmount);
+ expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
+ .to.be.bignumber.equal(partialFillAmount);
+ expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
+ .to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
+ });
+ it('should fill the valid orders with fees', async () => {
+ const fillableAmount = new BigNumber(5);
+ const makerFee = new BigNumber(1);
+ const takerFee = new BigNumber(2);
+ const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
+ makerTokenAddress, takerTokenAddress, makerFee, takerFee,
+ makerAddress, takerAddress, fillableAmount, feeRecipient,
+ );
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmount, shouldCheckTransfer);
+ expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient))
+ .to.be.bignumber.equal(makerFee.plus(takerFee));
+ });
+ });
+ });
+});
diff --git a/test/utils/blockchain_lifecycle.ts b/test/utils/blockchain_lifecycle.ts
index 68e169ac0..50eb57b95 100644
--- a/test/utils/blockchain_lifecycle.ts
+++ b/test/utils/blockchain_lifecycle.ts
@@ -17,4 +17,4 @@ export class BlockchainLifecycle {
throw new Error(`Snapshot with id #${this.snapshotId} failed to revert`);
}
}
-};
+}
diff --git a/test/utils/fill_scenarios.ts b/test/utils/fill_scenarios.ts
new file mode 100644
index 000000000..a44d6b18a
--- /dev/null
+++ b/test/utils/fill_scenarios.ts
@@ -0,0 +1,84 @@
+import * as BigNumber from 'bignumber.js';
+import {ZeroEx} from '../../src/0x.js';
+import {Token, SignedOrder} from '../../src/types';
+import {orderFactory} from '../utils/order_factory';
+import {constants} from './constants';
+
+export class FillScenarios {
+ private zeroEx: ZeroEx;
+ private userAddresses: string[];
+ private tokens: Token[];
+ private coinbase: string;
+ private zrxTokenAddress: string;
+ constructor(zeroEx: ZeroEx, userAddresses: string[], tokens: Token[], zrxTokenAddress: string) {
+ this.zeroEx = zeroEx;
+ this.userAddresses = userAddresses;
+ this.tokens = tokens;
+ this.coinbase = userAddresses[0];
+ this.zrxTokenAddress = zrxTokenAddress;
+ }
+ public async createFillableSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
+ makerAddress: string, takerAddress: string,
+ fillableAmount: BigNumber.BigNumber,
+ expirationUnixTimestampSec?: BigNumber.BigNumber):
+ Promise<SignedOrder> {
+ return this.createAsymmetricFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
+ fillableAmount, fillableAmount, expirationUnixTimestampSec,
+ );
+ }
+ public async createFillableSignedOrderWithFeesAsync(
+ makerTokenAddress: string, takerTokenAddress: string,
+ makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
+ makerAddress: string, takerAddress: string,
+ fillableAmount: BigNumber.BigNumber,
+ feeRecepient: string, expirationUnixTimestampSec?: BigNumber.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.BigNumber, takerFillableAmount: BigNumber.BigNumber,
+ expirationUnixTimestampSec?: BigNumber.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,
+ );
+ }
+ private async createAsymmetricFillableSignedOrderWithFeesAsync(
+ makerTokenAddress: string, takerTokenAddress: string,
+ makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
+ makerAddress: string, takerAddress: string,
+ makerFillableAmount: BigNumber.BigNumber, takerFillableAmount: BigNumber.BigNumber,
+ feeRecepient: string, expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
+ await this.zeroEx.token.transferAsync(makerTokenAddress, this.coinbase, makerAddress, makerFillableAmount);
+ await this.zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount);
+ await this.zeroEx.token.transferAsync(takerTokenAddress, this.coinbase, takerAddress, takerFillableAmount);
+ await this.zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount);
+
+ if (!makerFee.isZero()) {
+ await this.zeroEx.token.transferAsync(this.zrxTokenAddress, this.coinbase, makerAddress, makerFee);
+ await this.zeroEx.token.setProxyAllowanceAsync(this.zrxTokenAddress, makerAddress, makerFee);
+ }
+ if (!takerFee.isZero()) {
+ await this.zeroEx.token.transferAsync(this.zrxTokenAddress, this.coinbase, takerAddress, takerFee);
+ await this.zeroEx.token.setProxyAllowanceAsync(this.zrxTokenAddress, takerAddress, takerFee);
+ }
+
+ const prevTransactionSenderAccount = await this.zeroEx.getTransactionSenderAccountIfExistsAsync();
+ this.zeroEx.setTransactionSenderAccount(makerAddress);
+ const signedOrder = await orderFactory.createSignedOrderAsync(this.zeroEx,
+ makerAddress, takerAddress, makerFee, takerFee,
+ makerFillableAmount, makerTokenAddress, takerFillableAmount, takerTokenAddress,
+ feeRecepient, expirationUnixTimestampSec);
+ // We re-set the transactionSender to avoid introducing side-effects
+ this.zeroEx.setTransactionSenderAccount(prevTransactionSenderAccount as string);
+ return signedOrder;
+ }
+}
diff --git a/test/utils/order_factory.ts b/test/utils/order_factory.ts
new file mode 100644
index 000000000..373dbddc6
--- /dev/null
+++ b/test/utils/order_factory.ts
@@ -0,0 +1,43 @@
+import * as _ from 'lodash';
+import * as BigNumber from 'bignumber.js';
+import {SignedOrder, Token} from '../../src/types';
+import {ZeroEx} from '../../src/0x.js';
+import {constants} from './constants';
+import * as ExchangeArtifacts from '../../src/artifacts/Exchange.json';
+
+export const orderFactory = {
+ async createSignedOrderAsync(
+ zeroEx: ZeroEx,
+ maker: string,
+ taker: string,
+ makerFee: BigNumber.BigNumber,
+ takerFee: BigNumber.BigNumber,
+ makerTokenAmount: BigNumber.BigNumber,
+ makerTokenAddress: string,
+ takerTokenAmount: BigNumber.BigNumber,
+ takerTokenAddress: string,
+ feeRecipient: string,
+ expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
+ const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
+ expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSec) ?
+ defaultExpirationUnixTimestampSec :
+ expirationUnixTimestampSec;
+ const order = {
+ maker,
+ taker,
+ makerFee,
+ takerFee,
+ makerTokenAmount,
+ takerTokenAmount,
+ makerTokenAddress,
+ takerTokenAddress,
+ salt: ZeroEx.generatePseudoRandomSalt(),
+ feeRecipient,
+ expirationUnixTimestampSec,
+ };
+ const orderHash = await zeroEx.getOrderHashHexAsync(order);
+ const ecSignature = await zeroEx.signOrderHashAsync(orderHash);
+ const signedOrder: SignedOrder = _.assign(order, {ecSignature});
+ return signedOrder;
+ },
+};
diff --git a/test/utils/token_utils.ts b/test/utils/token_utils.ts
new file mode 100644
index 000000000..14788b299
--- /dev/null
+++ b/test/utils/token_utils.ts
@@ -0,0 +1,24 @@
+import * as _ from 'lodash';
+import {Token, ZeroExError} from '../../src/types';
+
+const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
+
+export class TokenUtils {
+ private tokens: Token[];
+ constructor(tokens: Token[]) {
+ this.tokens = tokens;
+ }
+ public getProtocolTokenOrThrow(): Token {
+ const zrxToken = _.find(this.tokens, {symbol: PROTOCOL_TOKEN_SYMBOL});
+ if (_.isUndefined(zrxToken)) {
+ throw new Error(ZeroExError.ZRX_NOT_IN_TOKEN_REGISTRY);
+ }
+ return zrxToken;
+ }
+ public getNonProtocolTokens(): Token[] {
+ const nonProtocolTokens = _.filter(this.tokens, token => {
+ return token.symbol !== PROTOCOL_TOKEN_SYMBOL;
+ });
+ return nonProtocolTokens;
+ }
+}