aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--circle.yml2
-rw-r--r--package.json2
-rw-r--r--src/0x.js.ts47
-rw-r--r--src/bignumber_config.ts9
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts83
-rw-r--r--src/schemas/signed_order_schema.ts50
-rw-r--r--src/types.ts55
-rw-r--r--src/utils/assert.ts3
-rw-r--r--src/utils/schema_validator.ts9
-rw-r--r--src/web3_wrapper.ts7
-rw-r--r--test/0x.js_test.ts47
-rw-r--r--test/exchange_wrapper_test.ts (renamed from test/exchange_wrapper.ts)64
-rw-r--r--test/utils/blockchain_lifecycle.ts2
-rw-r--r--test/utils/order.ts42
14 files changed, 349 insertions, 73 deletions
diff --git a/circle.yml b/circle.yml
index 448524aa1..4919516f2 100644
--- a/circle.yml
+++ b/circle.yml
@@ -4,7 +4,7 @@ machine:
test:
override:
- - node node_modules/ethereumjs-testrpc/bin/testrpc -m "concert load couple harbor equip island argue ramp clarify fence smart topic":
+ - npm run testrpc:
background: true
- git clone git@github.com:0xProject/contracts.git ../contracts
- cd ../contracts; git checkout 38c2b4c; npm install && npm run migrate
diff --git a/package.json b/package.json
index 6051759ea..7df7679c1 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"scripts": {
"prebuild": "npm run clean",
"build": "run-p build:*:prod",
- "lint": "tslint src/**/*.ts",
+ "lint": "tslint src/*.ts test/*.ts",
"test": "run-s clean test:commonjs",
"test:umd": "run-s substitute_umd_bundle run_mocha; npm run clean",
"test:coverage": "nyc npm run test --all",
diff --git a/src/0x.js.ts b/src/0x.js.ts
index d231c579e..25b4af777 100644
--- a/src/0x.js.ts
+++ b/src/0x.js.ts
@@ -1,5 +1,7 @@
import * as _ from 'lodash';
import * as BigNumber from 'bignumber.js';
+import {setBigNumberConfig} from './bignumber_config';
+setBigNumberConfig();
import * as ethUtil from 'ethereumjs-util';
import contract = require('truffle-contract');
import * as Web3 from 'web3';
@@ -15,6 +17,8 @@ import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper';
import {ecSignatureSchema} from './schemas/ec_signature_schema';
import {TokenWrapper} from './contract_wrappers/token_wrapper';
import {SolidityTypes, ECSignature, ZeroExError} from './types';
+import {Order} from './types';
+import {orderSchema} from './schemas/signed_order_schema';
const MAX_DIGITS_IN_UNSIGNED_256_INT = 78;
@@ -26,38 +30,23 @@ export class ZeroEx {
/**
* Computes the orderHash given the order parameters and returns it as a hex encoded string.
*/
- public static getOrderHashHex(exchangeContractAddr: string, makerAddr: string, takerAddr: string,
- tokenMAddress: string, tokenTAddress: string, feeRecipient: string,
- valueM: BigNumber.BigNumber, valueT: BigNumber.BigNumber,
- makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
- expiration: BigNumber.BigNumber, salt: BigNumber.BigNumber): string {
- takerAddr = _.isEmpty(takerAddr) ? constants.NULL_ADDRESS : takerAddr ;
- assert.isETHAddressHex('exchangeContractAddr', exchangeContractAddr);
- assert.isETHAddressHex('makerAddr', makerAddr);
- assert.isETHAddressHex('takerAddr', takerAddr);
- assert.isETHAddressHex('tokenMAddress', tokenMAddress);
- assert.isETHAddressHex('tokenTAddress', tokenTAddress);
- assert.isETHAddressHex('feeRecipient', feeRecipient);
- assert.isBigNumber('valueM', valueM);
- assert.isBigNumber('valueT', valueT);
- assert.isBigNumber('makerFee', makerFee);
- assert.isBigNumber('takerFee', takerFee);
- assert.isBigNumber('expiration', expiration);
- assert.isBigNumber('salt', salt);
+ public static getOrderHashHex(exchangeContractAddr: string, order: Order): string {
+ assert.doesConformToSchema('order', JSON.parse(JSON.stringify(order)), orderSchema);
+ const taker = _.isEmpty(order.taker) ? constants.NULL_ADDRESS : order.taker ;
const orderParts = [
{value: exchangeContractAddr, type: SolidityTypes.address},
- {value: makerAddr, type: SolidityTypes.address},
- {value: takerAddr, type: SolidityTypes.address},
- {value: tokenMAddress, type: SolidityTypes.address},
- {value: tokenTAddress, type: SolidityTypes.address},
- {value: feeRecipient, type: SolidityTypes.address},
- {value: utils.bigNumberToBN(valueM), type: SolidityTypes.uint256},
- {value: utils.bigNumberToBN(valueT), type: SolidityTypes.uint256},
- {value: utils.bigNumberToBN(makerFee), type: SolidityTypes.uint256},
- {value: utils.bigNumberToBN(takerFee), type: SolidityTypes.uint256},
- {value: utils.bigNumberToBN(expiration), type: SolidityTypes.uint256},
- {value: utils.bigNumberToBN(salt), type: SolidityTypes.uint256},
+ {value: order.maker, type: SolidityTypes.address},
+ {value: order.taker, type: SolidityTypes.address},
+ {value: order.makerTokenAddress, type: SolidityTypes.address},
+ {value: order.takerTokenAddress, type: SolidityTypes.address},
+ {value: order.feeRecipient, type: SolidityTypes.address},
+ {value: utils.bigNumberToBN(order.makerTokenAmount), type: SolidityTypes.uint256},
+ {value: utils.bigNumberToBN(order.takerTokenAmount), type: SolidityTypes.uint256},
+ {value: utils.bigNumberToBN(order.makerFee), type: SolidityTypes.uint256},
+ {value: utils.bigNumberToBN(order.takerFee), type: SolidityTypes.uint256},
+ {value: utils.bigNumberToBN(order.expirationUnixTimestampSec), type: SolidityTypes.uint256},
+ {value: utils.bigNumberToBN(order.salt), type: SolidityTypes.uint256},
];
const types = _.map(orderParts, o => o.type);
const values = _.map(orderParts, o => o.value);
diff --git a/src/bignumber_config.ts b/src/bignumber_config.ts
new file mode 100644
index 000000000..9641159d0
--- /dev/null
+++ b/src/bignumber_config.ts
@@ -0,0 +1,9 @@
+import * as BigNumber from 'bignumber.js';
+
+export function setBigNumberConfig() {
+ // By default BigNumber's `toString` method converts to exponential notation if the value has
+ // more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number
+ BigNumber.config({
+ EXPONENTIAL_AT: 1000,
+ });
+}
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts
index 3f6eb0dab..3f6210894 100644
--- a/src/contract_wrappers/exchange_wrapper.ts
+++ b/src/contract_wrappers/exchange_wrapper.ts
@@ -1,12 +1,31 @@
import * as _ from 'lodash';
import {Web3Wrapper} from '../web3_wrapper';
-import {ECSignature, ZeroExError, ExchangeContract} from '../types';
+import {
+ ECSignature,
+ ExchangeContract,
+ ExchangeContractErrs,
+ OrderValues,
+ OrderAddresses,
+ SignedOrder,
+ ContractEvent,
+} from '../types';
import {assert} from '../utils/assert';
import {ContractWrapper} from './contract_wrapper';
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
+import {signedOrderSchema} from '../schemas/signed_order_schema';
+import {ContractResponse} from '../types';
+import {constants} from '../utils/constants';
export class ExchangeWrapper extends ContractWrapper {
+ private exchangeContractErrToMsg = {
+ [ExchangeContractErrs.ERROR_FILL_EXPIRED]: 'The order you attempted to fill is expired',
+ [ExchangeContractErrs.ERROR_CANCEL_EXPIRED]: 'The order you attempted to cancel is expired',
+ [ExchangeContractErrs.ERROR_FILL_NO_VALUE]: 'This order has already been filled or cancelled',
+ [ExchangeContractErrs.ERROR_CANCEL_NO_VALUE]: 'This order has already been filled or cancelled',
+ [ExchangeContractErrs.ERROR_FILL_TRUNCATION]: 'The rounding error was too large when filling this order',
+ [ExchangeContractErrs.ERROR_FILL_BALANCE_ALLOWANCE]: 'Maker or taker has insufficient balance or allowance',
+ };
private exchangeContractIfExists?: ExchangeContract;
constructor(web3Wrapper: Web3Wrapper) {
super(web3Wrapper);
@@ -20,23 +39,73 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('ecSignature', ecSignature, ecSignatureSchema);
assert.isETHAddressHex('signerAddressHex', signerAddressHex);
- const senderAddressIfExists = await this.web3Wrapper.getSenderAddressIfExistsAsync();
- assert.assert(!_.isUndefined(senderAddressIfExists), ZeroExError.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+ const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync();
+ const exchangeInstance = await this.getExchangeContractAsync();
- const exchangeContract = await this.getExchangeContractAsync();
-
- const isValidSignature = await exchangeContract.isValidSignature.call(
+ const isValidSignature = await exchangeInstance.isValidSignature.call(
signerAddressHex,
dataHex,
ecSignature.v,
ecSignature.r,
ecSignature.s,
{
- from: senderAddressIfExists,
+ from: senderAddress,
},
);
return isValidSignature;
}
+ public async fillOrderAsync(signedOrder: SignedOrder, fillAmount: BigNumber.BigNumber,
+ shouldCheckTransfer: boolean = true): Promise<void> {
+ assert.doesConformToSchema('signedOrder', JSON.parse(JSON.stringify(signedOrder)), signedOrderSchema);
+ assert.isBigNumber('fillAmount', fillAmount);
+ assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
+
+ const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync();
+ const exchangeInstance = await this.getExchangeInstanceOrThrowAsync();
+
+ const taker = _.isUndefined(signedOrder.taker) ? constants.NULL_ADDRESS : signedOrder.taker;
+
+ const orderAddresses: OrderAddresses = [
+ signedOrder.maker,
+ taker,
+ signedOrder.makerTokenAddress,
+ signedOrder.takerTokenAddress,
+ signedOrder.feeRecipient,
+ ];
+ const orderValues: OrderValues = [
+ signedOrder.makerTokenAmount,
+ signedOrder.takerTokenAmount,
+ signedOrder.makerFee,
+ signedOrder.takerFee,
+ signedOrder.expirationUnixTimestampSec,
+ signedOrder.salt,
+ ];
+ const response: ContractResponse = await exchangeInstance.fill(
+ orderAddresses,
+ orderValues,
+ fillAmount,
+ shouldCheckTransfer,
+ signedOrder.ecSignature.v,
+ signedOrder.ecSignature.r,
+ signedOrder.ecSignature.s,
+ {
+ from: senderAddress,
+ },
+ );
+ this.throwErrorLogsAsErrors(response.logs);
+ }
+ private async getExchangeInstanceOrThrowAsync(): Promise<ExchangeContract> {
+ const contractInstance = await this.instantiateContractIfExistsAsync((ExchangeArtifacts as any));
+ return contractInstance as ExchangeContract;
+ }
+ private throwErrorLogsAsErrors(logs: ContractEvent[]): void {
+ const errEvent = _.find(logs, {event: 'LogError'});
+ if (!_.isUndefined(errEvent)) {
+ const errCode = errEvent.args.errorId.toNumber();
+ const humanReadableErrMessage = this.exchangeContractErrToMsg[errCode];
+ throw new Error(humanReadableErrMessage);
+ }
+ }
private async getExchangeContractAsync(): Promise<ExchangeContract> {
if (!_.isUndefined(this.exchangeContractIfExists)) {
return this.exchangeContractIfExists;
diff --git a/src/schemas/signed_order_schema.ts b/src/schemas/signed_order_schema.ts
new file mode 100644
index 000000000..dc7e51e40
--- /dev/null
+++ b/src/schemas/signed_order_schema.ts
@@ -0,0 +1,50 @@
+export const addressSchema = {
+ id: '/addressSchema',
+ type: 'string',
+ pattern: '^0[xX][0-9A-Fa-f]{40}$',
+};
+
+export const numberSchema = {
+ id: '/numberSchema',
+ type: 'string',
+ format: '\d+(\.\d+)?',
+};
+
+export const orderSchema = {
+ id: '/orderSchema',
+ properties: {
+ maker: {$ref: '/addressSchema'},
+ taker: {$ref: '/addressSchema'},
+
+ makerFee: {$ref: '/numberSchema'},
+ takerFee: {$ref: '/numberSchema'},
+
+ makerTokenAmount: {$ref: '/numberSchema'},
+ takerTokenAmount: {$ref: '/numberSchema'},
+
+ makerTokenAddress: {$ref: '/addressSchema'},
+ takerTokenAddress: {$ref: '/addressSchema'},
+
+ salt: {$ref: '/numberSchema'},
+ feeRecipient: {$ref: '/addressSchema'},
+ expirationUnixTimestampSec: {$ref: '/numberSchema'},
+ },
+ required: [
+ 'maker', /*'taker',*/ 'makerFee', 'takerFee', 'makerTokenAmount', 'takerTokenAmount',
+ 'salt', 'feeRecipient', 'expirationUnixTimestampSec',
+ ],
+ type: 'object',
+};
+
+export const signedOrderSchema = {
+ id: '/signedOrderSchema',
+ allOf: [
+ { $ref: '/orderSchema' },
+ {
+ properties: {
+ ecSignature: {$ref: '/ECSignature'},
+ },
+ required: ['ecSignature'],
+ },
+ ],
+};
diff --git a/src/types.ts b/src/types.ts
index 5b514bdc4..8c378816f 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -27,8 +27,23 @@ export interface ECSignature {
s: string;
}
+export type OrderAddresses = [string, string, string, string, string];
+
+export type OrderValues = [
+ BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber,
+ BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber
+];
+
+export interface TxData {
+ from: string;
+}
+
export interface ExchangeContract {
isValidSignature: any;
+ fill: (
+ orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
+ shouldCheckTransfer: boolean, v: number, r: string, s: string, txData: TxData,
+ ) => ContractResponse;
}
export interface TokenContract {
@@ -57,6 +72,42 @@ export const SolidityTypes = strEnum([
]);
export type SolidityTypes = keyof typeof SolidityTypes;
+export enum ExchangeContractErrs {
+ ERROR_FILL_EXPIRED, // Order has already expired
+ ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled
+ ERROR_FILL_TRUNCATION, // Rounding error too large
+ ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer
+ ERROR_CANCEL_EXPIRED, // Order has already expired
+ ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled
+}
+
+export interface ContractResponse {
+ logs: ContractEvent[];
+}
+
+export interface ContractEvent {
+ event: string;
+ args: any;
+}
+
+export interface Order {
+ maker: string;
+ taker?: string;
+ makerFee: BigNumber.BigNumber;
+ takerFee: BigNumber.BigNumber;
+ makerTokenAmount: BigNumber.BigNumber;
+ takerTokenAmount: BigNumber.BigNumber;
+ makerTokenAddress: string;
+ takerTokenAddress: string;
+ salt: BigNumber.BigNumber;
+ feeRecipient: string;
+ expirationUnixTimestampSec: BigNumber.BigNumber;
+}
+
+export interface SignedOrder extends Order {
+ ecSignature: ECSignature;
+}
+
// [address, name, symbol, projectUrl, decimals, ipfsHash, swarmHash]
export type TokenMetadata = [string, string, string, string, BigNumber.BigNumber, string, string];
@@ -66,9 +117,9 @@ export interface Token {
symbol: string;
decimals: number;
url: string;
-};
+}
export interface TxOpts {
from: string;
gas?: number;
-};
+}
diff --git a/src/utils/assert.ts b/src/utils/assert.ts
index 1baf572d1..aeed1c6dc 100644
--- a/src/utils/assert.ts
+++ b/src/utils/assert.ts
@@ -27,6 +27,9 @@ export const assert = {
isNumber(variableName: string, value: number): void {
this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value));
},
+ isBoolean(variableName: string, value: boolean): void {
+ this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value));
+ },
doesConformToSchema(variableName: string, value: object, schema: Schema): void {
const schemaValidator = new SchemaValidator();
const validationResult = schemaValidator.validate(value, schema);
diff --git a/src/utils/schema_validator.ts b/src/utils/schema_validator.ts
index 8132f7414..cf45d0343 100644
--- a/src/utils/schema_validator.ts
+++ b/src/utils/schema_validator.ts
@@ -1,14 +1,19 @@
import {Validator, ValidatorResult} from 'jsonschema';
import {ecSignatureSchema, ecSignatureParameter} from '../schemas/ec_signature_schema';
+import {addressSchema, numberSchema, orderSchema, signedOrderSchema} from '../schemas/signed_order_schema';
import {tokenSchema} from '../schemas/token_schema';
export class SchemaValidator {
private validator: Validator;
constructor() {
this.validator = new Validator();
- this.validator.addSchema(ecSignatureParameter, ecSignatureParameter.id);
- this.validator.addSchema(ecSignatureSchema, ecSignatureSchema.id);
this.validator.addSchema(tokenSchema, tokenSchema.id);
+ this.validator.addSchema(orderSchema, orderSchema.id);
+ this.validator.addSchema(numberSchema, numberSchema.id);
+ this.validator.addSchema(addressSchema, addressSchema.id);
+ this.validator.addSchema(ecSignatureSchema, ecSignatureSchema.id);
+ this.validator.addSchema(signedOrderSchema, signedOrderSchema.id);
+ this.validator.addSchema(ecSignatureParameter, ecSignatureParameter.id);
}
public validate(instance: object, schema: Schema): ValidatorResult {
return this.validator.validate(instance, schema);
diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts
index e65f29b56..79fd166ec 100644
--- a/src/web3_wrapper.ts
+++ b/src/web3_wrapper.ts
@@ -2,6 +2,8 @@ import * as _ from 'lodash';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
+import {ZeroExError} from './types';
+import {assert} from './utils/assert';
export class Web3Wrapper {
private web3: Web3;
@@ -23,6 +25,11 @@ export class Web3Wrapper {
const firstAccount = await this.getFirstAddressIfExistsAsync();
return firstAccount;
}
+ public async getSenderAddressOrThrowAsync(): Promise<string> {
+ const senderAddressIfExists = await this.getSenderAddressIfExistsAsync();
+ assert.assert(!_.isUndefined(senderAddressIfExists), ZeroExError.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+ return senderAddressIfExists as string;
+ }
public async getFirstAddressIfExistsAsync(): Promise<string|undefined> {
const addresses = await promisify(this.web3.eth.getAccounts)();
if (_.isEmpty(addresses)) {
diff --git a/test/0x.js_test.ts b/test/0x.js_test.ts
index 5d23d7094..a84785f4b 100644
--- a/test/0x.js_test.ts
+++ b/test/0x.js_test.ts
@@ -7,6 +7,7 @@ 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());
@@ -43,38 +44,28 @@ describe('ZeroEx library', () => {
});
describe('#getOrderHash', () => {
const expectedOrderHash = '0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7';
+ const order: Order = {
+ maker: 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),
+ };
+ const exchangeAddress = constants.NULL_ADDRESS;
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),
- );
+ const orderHash = ZeroEx.getOrderHashHex(exchangeAddress, order);
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),
- );
+ const orderWithZeroTaker = _.assign(order, {
+ taker: constants.NULL_ADDRESS,
+ });
+ const orderHash = ZeroEx.getOrderHashHex(exchangeAddress, orderWithZeroTaker);
expect(orderHash).to.be.equal(expectedOrderHash);
});
});
diff --git a/test/exchange_wrapper.ts b/test/exchange_wrapper_test.ts
index e42454089..5138e140f 100644
--- a/test/exchange_wrapper.ts
+++ b/test/exchange_wrapper_test.ts
@@ -2,16 +2,26 @@ import 'mocha';
import * as chai from 'chai';
import {web3Factory} from './utils/web3_factory';
import {ZeroEx} from '../src/0x.js';
+import promisify = require('es6-promisify');
+import * as _ from 'lodash';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
+import * as BigNumber from 'bignumber.js';
+import {createSignedOrder} from './utils/order';
+import {Token} from '../src/types';
+import * as Web3 from 'web3';
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
describe('ExchangeWrapper', () => {
let zeroEx: ZeroEx;
+ let userAddresses: string[];
+ let web3: Web3;
before(async () => {
- const web3 = web3Factory.create();
+ web3 = web3Factory.create();
zeroEx = new ZeroEx(web3);
+ userAddresses = await promisify(web3.eth.getAccounts)();
+ web3.eth.defaultAccount = userAddresses[0];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -80,7 +90,7 @@ describe('ExchangeWrapper', () => {
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 wrongSignature = {...signature, v: 28};
const isValid = await zeroEx.exchange.isValidSignatureAsync(dataHex, wrongSignature, address);
expect(isValid).to.be.false;
});
@@ -89,4 +99,54 @@ describe('ExchangeWrapper', () => {
expect(isValid).to.be.true;
});
});
+ describe('#fillOrderAsync', () => {
+ let tokens: Token[];
+ const setBalance = async (toAddress: string,
+ amountInBaseUnits: BigNumber.BigNumber|number,
+ symbol: string) => {
+ const amount = _.isNumber(amountInBaseUnits) ? new BigNumber(amountInBaseUnits) : amountInBaseUnits;
+ const token = _.find(tokens, {symbol});
+ if (_.isUndefined(token)) {
+ throw new Error(`Token ${symbol} not found`);
+ } else {
+ await zeroEx.token.transferAsync(token.address, userAddresses[0], toAddress, amount);
+ }
+ };
+ const setAllowance = async (ownerAddress: string,
+ amountInBaseUnits: BigNumber.BigNumber|number,
+ symbol: string) => {
+ const amount = _.isNumber(amountInBaseUnits) ? new BigNumber(amountInBaseUnits) : amountInBaseUnits;
+ const token = _.find(tokens, {symbol});
+ if (_.isUndefined(token)) {
+ throw new Error(`Token ${symbol} not found`);
+ } else {
+ await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amount);
+ }
+ };
+ before('fetch tokens', async () => {
+ tokens = await zeroEx.tokenRegistry.getTokensAsync();
+ });
+ describe('failed fills', () => {
+ it('should throw when the fill amount is zero', async () => {
+ const signedOrder = await createSignedOrder(zeroEx, tokens, 5, 'MLN', 5, 'GNT');
+ const fillAmount = new BigNumber(0);
+ expect(zeroEx.exchange.fillOrderAsync(signedOrder, fillAmount))
+ .to.be.rejectedWith('This order has already been filled or cancelled');
+ });
+ });
+ describe('successful fills', () => {
+ afterEach('reset default account', () => {
+ web3.eth.defaultAccount = userAddresses[0];
+ });
+ it('should fill the valid order', async () => {
+ await setAllowance(userAddresses[0], 5, 'MLN');
+ await setBalance(userAddresses[1], 5, 'GNT');
+ await setAllowance(userAddresses[1], 5, 'GNT');
+ const signedOrder = await createSignedOrder(zeroEx, tokens, 5, 'MLN', 5, 'GNT');
+ const fillAmount = new BigNumber(5);
+ web3.eth.defaultAccount = userAddresses[1];
+ await zeroEx.exchange.fillOrderAsync(signedOrder, fillAmount);
+ });
+ });
+ });
});
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/order.ts b/test/utils/order.ts
new file mode 100644
index 000000000..d775dc616
--- /dev/null
+++ b/test/utils/order.ts
@@ -0,0 +1,42 @@
+import {SignedOrder, Token} from '../../src/types';
+import * as BigNumber from 'bignumber.js';
+import * as _ from 'lodash';
+import {ZeroEx} from '../../src/0x.js';
+import {constants} from './constants';
+
+export async function createSignedOrder(
+ zeroEx: ZeroEx,
+ tokens: Token[],
+ makerTokenAmount: BigNumber.BigNumber|number,
+ makerTokenSymbol: string,
+ takerTokenAmount: BigNumber.BigNumber|number,
+ takerTokenSymbol: string): Promise<SignedOrder> {
+ // TODO: fetch properly
+ const EXCHANGE_ADDRESS = '0xb69e673309512a9d726f87304c6984054f87a93b';
+ const INF_TIMESTAMP = 2524604400;
+ const makerToken = _.find(tokens, {symbol: makerTokenSymbol});
+ const takerToken = _.find(tokens, {symbol: takerTokenSymbol});
+ if (_.isUndefined(makerToken)) {
+ throw new Error(`Token ${makerTokenSymbol} not found`);
+ }
+ if (_.isUndefined(takerToken)) {
+ throw new Error(`Token ${takerTokenSymbol} not found`);
+ }
+ const order = {
+ maker: '0x5409ed021d9299bf6814279a6a1411a7e866a631',
+ taker: undefined,
+ makerFee: new BigNumber(0),
+ takerFee: new BigNumber(0),
+ makerTokenAmount: _.isNumber(makerTokenAmount) ? new BigNumber(makerTokenAmount) : makerTokenAmount,
+ takerTokenAmount: _.isNumber(takerTokenAmount) ? new BigNumber(takerTokenAmount) : takerTokenAmount,
+ makerTokenAddress: makerToken.address,
+ takerTokenAddress: takerToken.address,
+ salt: ZeroEx.generatePseudoRandomSalt(),
+ feeRecipient: constants.NULL_ADDRESS,
+ expirationUnixTimestampSec: new BigNumber(INF_TIMESTAMP),
+ };
+ const orderHash = ZeroEx.getOrderHashHex(EXCHANGE_ADDRESS, order);
+ const ecSignature = await zeroEx.signOrderHashAsync(orderHash);
+ const signedOrder: SignedOrder = _.assign(order, {ecSignature});
+ return signedOrder;
+}