aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--circle.yml2
-rw-r--r--package.json7
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts83
-rw-r--r--src/schemas/signed_order_schema.ts50
-rw-r--r--src/types.ts57
-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/exchange_wrapper_test.ts (renamed from test/exchange_wrapper.ts)10
-rw-r--r--test/fixtures/orders/5_MKR_for_42_MLN.json35
-rw-r--r--test/utils/blockchain_lifecycle.ts2
-rw-r--r--test/utils/order.ts21
12 files changed, 271 insertions, 15 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 75a55b899..c232e2828 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",
@@ -27,11 +27,12 @@
"build:umd:dev": "webpack",
"build:umd:prod": "webpack -p",
"build:commonjs:dev": "tsc; copyfiles -u 2 ./src/artifacts/*.json ../0x.js/lib/src/artifacts;",
- "test:commonjs": "run-s build:commonjs:dev run_mocha",
+ "test:commonjs": "run-s build:commonjs:dev copy_fixtures run_mocha",
"pretest:umd": "run-s clean build:*:dev",
"substitute_umd_bundle": "npm run remove_src_files_not_used_by_tests; shx mv _bundles/* lib/src",
"remove_src_files_not_used_by_tests": "find ./lib/src \\( -path ./lib/src/utils -o -path ./lib/src/schemas -o -path \"./lib/src/types.*\" \\) -prune -o -type f -print | xargs rm",
- "run_mocha": "mocha lib/test/**/*_test.js"
+ "run_mocha": "mocha lib/test/**/*_test.js",
+ "copy_fixtures": "shx cp -r ./test/fixtures ./lib/test/fixtures"
},
"config": {
"artifacts": "Proxy Exchange TokenRegistry Token Mintable EtherToken",
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts
index 3f6eb0dab..66e53a7d4 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<ContractResponse> {
+ assert.doesConformToSchema('signedOrder', JSON.parse(JSON.stringify(signedOrder)), signedOrderSchema);
+ 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);
+ return response;
+ }
+ 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 6fce95706..2ada20e3c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -26,8 +26,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 TokenRegistryContract {
@@ -45,6 +60,46 @@ 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];
@@ -54,4 +109,4 @@ export interface Token {
symbol: string;
decimals: number;
url: string;
-};
+}
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..361f28476 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/exchange_wrapper.ts b/test/exchange_wrapper_test.ts
index 55b84ce36..b97a62100 100644
--- a/test/exchange_wrapper.ts
+++ b/test/exchange_wrapper_test.ts
@@ -4,6 +4,9 @@ import chaiAsPromised = require('chai-as-promised');
import {web3Factory} from './utils/web3_factory';
import {ZeroEx} from '../src/0x.js';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
+import * as OrderJSON from './fixtures/orders/5_MKR_for_42_MLN.json';
+import * as BigNumber from 'bignumber.js';
+import {signedOrderFromJSON} from './utils/order';
const expect = chai.expect;
chai.use(chaiAsPromised);
@@ -91,4 +94,11 @@ describe('ExchangeWrapper', () => {
expect(isValid).to.be.true;
});
});
+ describe('#fillOrderAsync', () => {
+ const fillAmount = new BigNumber(1);
+ const signedOrder = signedOrderFromJSON(OrderJSON);
+ it('fillsOrder', async () => {
+ // const orderFillResponse = await zeroEx.exchange.fillOrderAsync(signedOrder, fillAmount);
+ });
+ });
});
diff --git a/test/fixtures/orders/5_MKR_for_42_MLN.json b/test/fixtures/orders/5_MKR_for_42_MLN.json
new file mode 100644
index 000000000..2c2a3d73e
--- /dev/null
+++ b/test/fixtures/orders/5_MKR_for_42_MLN.json
@@ -0,0 +1,35 @@
+{
+ "maker": {
+ "address": "0xffa119a5761eb93eacfe5d2b0e8944648c3d7164",
+ "token": {
+ "name": "MakerDAO",
+ "symbol": "MKR",
+ "decimals": 18,
+ "address": "0x1dad4783cf3fe3085c1426157ab175a6119a04ba"
+ },
+ "amount": "5000000000000000000",
+ "feeAmount": "0"
+ },
+ "taker": {
+ "address": "",
+ "token": {
+ "name": "Melon Token",
+ "symbol": "MLN",
+ "decimals": 18,
+ "address": "0x323b5d4c32345ced77393b3530b1eed0f346429d"
+ },
+ "amount": "42000000000000000000",
+ "feeAmount": "0"
+ },
+ "expiration": "1496181600",
+ "feeRecipient": "0x0000000000000000000000000000000000000000",
+ "salt": "28894038927316056783595066163529969528517282468995826064292810658599101267743",
+ "signature": {
+ "v": 28,
+ "r": "0x1e84173b09bc51e4e3f923718d747bd91c3584cfa3556d79294891ddd740e819",
+ "s": "0x36c0f453a01487f49e16157703cacb6cee08d337549f805c25d3f78221823eed",
+ "hash": "0x3f64711f39393f7f60a0980e3effba087c15bb9b1cbf678ede63a69798e5dc14"
+ },
+ "exchangeContract": "0x9ce1a5e2311f9b8b8e6b40ed20b5b090de4a4c4d",
+ "networkId": 42
+} \ No newline at end of file
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..dc0be670c
--- /dev/null
+++ b/test/utils/order.ts
@@ -0,0 +1,21 @@
+import {SignedOrder} from '../../lib/src/types';
+import * as BigNumber from 'bignumber.js';
+import * as _ from 'lodash';
+
+export function signedOrderFromJSON(signedOrderJSON: any): SignedOrder {
+ const signedOrder = {
+ maker: signedOrderJSON.maker.address,
+ taker: _.isEmpty(signedOrderJSON.taker.address) ? undefined : signedOrderJSON.taker.address,
+ makerTokenAddress: signedOrderJSON.maker.token.address,
+ takerTokenAddress: signedOrderJSON.taker.token.address,
+ makerTokenAmount: new BigNumber(signedOrderJSON.maker.amount),
+ takerTokenAmount: new BigNumber(signedOrderJSON.taker.amount),
+ makerFee: new BigNumber(signedOrderJSON.maker.feeAmount),
+ takerFee: new BigNumber(signedOrderJSON.taker.feeAmount),
+ expirationUnixTimestampSec: new BigNumber(signedOrderJSON.expiration),
+ feeRecipient: signedOrderJSON.feeRecipient,
+ ecSignature: signedOrderJSON.signature,
+ salt: new BigNumber(signedOrderJSON.salt),
+ };
+ return signedOrder;
+}