From f2f39d9eb2822b71aa7c5cf6f3b2ef006a83ad03 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 25 May 2017 17:24:29 +0200 Subject: Port getOrderHash --- package.json | 2 +- src/ts/0x.js.ts | 40 ++++++++++++++++++++++++++++++++++++++++ src/ts/globals.d.ts | 5 +++++ src/ts/types.ts | 21 +++++++++++++++++++++ src/ts/utils/constants.ts | 3 +++ test/0x.js.ts | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/ts/types.ts create mode 100644 src/ts/utils/constants.ts diff --git a/package.json b/package.json index 557d6eeff..e6281afae 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "@types/chai": "^3.5.2", "@types/mocha": "^2.2.41", "@types/node": "^7.0.22", - "@types/lodash": "^4.14.64", "awesome-typescript-loader": "^3.1.3", "bignumber.js": "^4.0.2", "chai": "^3.5.0", @@ -55,6 +54,7 @@ }, "dependencies": { "bignumber.js": "^4.0.2", + "ethereumjs-abi": "^0.6.4", "ethereumjs-util": "^5.1.1", "jsonschema": "^1.1.1", "lodash": "^4.17.4", diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts index ead1f56df..82dbce22d 100644 --- a/src/ts/0x.js.ts +++ b/src/ts/0x.js.ts @@ -1,8 +1,12 @@ import * as BigNumber from 'bignumber.js'; +import * as BN from 'bn.js'; import * as ethUtil from 'ethereumjs-util'; +import * as ethABI from 'ethereumjs-abi'; import * as _ from 'lodash'; +import {constants} from './utils/constants'; import {assert} from './utils/assert'; import {ECSignatureSchema} from './schemas/ec_signature_schema'; +import {SolidityTypes} from './types'; /** * Elliptic Curve signature @@ -16,6 +20,42 @@ export interface ECSignature { const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export class ZeroEx { + public static getOrderHash(exchangeContractAddr: string, makerAddr: string, takerAddr: string, + depositTokenAddr: string, receiveTokenAddr: string, feeRecipient: string, + depositAmt: BigNumber.BigNumber, receiveAmt: BigNumber.BigNumber, + makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber, + expiration: BigNumber.BigNumber, salt: BigNumber.BigNumber): string { + takerAddr = takerAddr !== '' ? takerAddr : constants.NULL_ADDRESS; + assert.isETHAddressHex('exchangeContractAddr', exchangeContractAddr); + assert.isETHAddressHex('makerAddr', makerAddr); + assert.isETHAddressHex('takerAddr', takerAddr); + assert.isETHAddressHex('depositTokenAddr', depositTokenAddr); + assert.isETHAddressHex('receiveTokenAddr', receiveTokenAddr); + assert.isETHAddressHex('feeRecipient', feeRecipient); + assert.isBigNumber('depositAmt', depositAmt); + assert.isBigNumber('receiveAmt', receiveAmt); + assert.isBigNumber('makerFee', makerFee); + assert.isBigNumber('takerFee', takerFee); + assert.isBigNumber('expiration', expiration); + assert.isBigNumber('salt', salt); + const orderParts = [ + {value: exchangeContractAddr, type: SolidityTypes.address}, + {value: makerAddr, type: SolidityTypes.address}, + {value: takerAddr, type: SolidityTypes.address}, + {value: depositTokenAddr, type: SolidityTypes.address}, + {value: receiveTokenAddr, type: SolidityTypes.address}, + {value: feeRecipient, type: SolidityTypes.address}, + {value: new BN(depositAmt.toString(), 10), type: SolidityTypes.uint256}, + {value: new BN(receiveAmt.toString(), 10), type: SolidityTypes.uint256}, + {value: new BN(makerFee.toString(), 10), type: SolidityTypes.uint256}, + {value: new BN(takerFee.toString(), 10), type: SolidityTypes.uint256}, + {value: new BN(expiration.toString(), 10), type: SolidityTypes.uint256}, + {value: new BN(salt.toString(), 10), type: SolidityTypes.uint256}, + ]; + const hashBuff = ethABI.soliditySHA3(_.map(orderParts, 'type'), _.map(orderParts, 'value')); + const buffHashHex = ethUtil.bufferToHex(hashBuff); + return buffHashHex; + } /** * Verifies that the elliptic curve signature `signature` was generated * by signing `data` with the private key corresponding to the `signerAddressHex` address. diff --git a/src/ts/globals.d.ts b/src/ts/globals.d.ts index 796812c87..a50c635fb 100644 --- a/src/ts/globals.d.ts +++ b/src/ts/globals.d.ts @@ -1,4 +1,5 @@ declare module 'chai-bignumber'; +declare module 'bn.js'; declare interface Schema { id: string; @@ -23,3 +24,7 @@ declare module 'ethereumjs-util' { const pubToAddress: (pubKey: string) => Buffer; const isValidAddress: (address: string) => boolean; } + +declare module 'ethereumjs-abi' { + const soliditySHA3: (argTypes: string[], args: any) => Buffer; +} diff --git a/src/ts/types.ts b/src/ts/types.ts new file mode 100644 index 000000000..c3da8d81e --- /dev/null +++ b/src/ts/types.ts @@ -0,0 +1,21 @@ +import * as _ from 'lodash'; +import * as BigNumber from 'bignumber.js'; + +// Utility function to create a K:V from a list of strings +// Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html +function strEnum(values: string[]): {[key: string]: string} { + return _.reduce(values, (result, key) => { + result[key] = key; + return result; + }, Object.create(null)); +} + +export const SolidityTypes = strEnum([ + 'address', + 'uint256', + 'uint8', + 'string', + 'bool', +]); + +export type SolidityTypes = keyof typeof SolidityTypes; diff --git a/src/ts/utils/constants.ts b/src/ts/utils/constants.ts new file mode 100644 index 000000000..60af7b674 --- /dev/null +++ b/src/ts/utils/constants.ts @@ -0,0 +1,3 @@ +export const constants = { + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', +} diff --git a/test/0x.js.ts b/test/0x.js.ts index a913fd6b5..07c58f1a5 100644 --- a/test/0x.js.ts +++ b/test/0x.js.ts @@ -3,12 +3,49 @@ import * as chai from 'chai'; import 'mocha'; import * as BigNumber from 'bignumber.js'; import ChaiBigNumber = require('chai-bignumber'); +import {constants} from '../src/ts/utils/constants'; // Use BigNumber chai add-on chai.use(ChaiBigNumber()); const expect = chai.expect; describe('ZeroEx library', () => { + describe('#getOrderHash', () => { + it('defaults takerAddress to NULL address', () => { + const orderHash = ZeroEx.getOrderHash( + 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('0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7'); + }); + it('calculates the order hash', () => { + const orderHash = ZeroEx.getOrderHash( + 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('0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7'); + }); + }); describe('#isValidSignature', () => { // This test data was borrowed from the JSON RPC documentation // Source: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign -- cgit v1.2.3 From 183e8a71389737dc1c535b3625c949307607b9b9 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 25 May 2017 20:29:33 +0200 Subject: Address feedback --- src/ts/0x.js.ts | 57 ++++++++++++++++++++++++++++++++++++--------------------- src/ts/types.ts | 1 - test/0x.js.ts | 9 +++++---- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts index 82dbce22d..893d95de6 100644 --- a/src/ts/0x.js.ts +++ b/src/ts/0x.js.ts @@ -20,41 +20,47 @@ export interface ECSignature { const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export class ZeroEx { - public static getOrderHash(exchangeContractAddr: string, makerAddr: string, takerAddr: string, - depositTokenAddr: string, receiveTokenAddr: string, feeRecipient: string, - depositAmt: BigNumber.BigNumber, receiveAmt: BigNumber.BigNumber, - makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber, - expiration: BigNumber.BigNumber, salt: BigNumber.BigNumber): string { - takerAddr = takerAddr !== '' ? takerAddr : constants.NULL_ADDRESS; + /** + * 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('depositTokenAddr', depositTokenAddr); - assert.isETHAddressHex('receiveTokenAddr', receiveTokenAddr); + assert.isETHAddressHex('tokenMAddress', tokenMAddress); + assert.isETHAddressHex('tokenTAddress', tokenTAddress); assert.isETHAddressHex('feeRecipient', feeRecipient); - assert.isBigNumber('depositAmt', depositAmt); - assert.isBigNumber('receiveAmt', receiveAmt); + assert.isBigNumber('valueM', valueM); + assert.isBigNumber('valueT', valueT); assert.isBigNumber('makerFee', makerFee); assert.isBigNumber('takerFee', takerFee); assert.isBigNumber('expiration', expiration); assert.isBigNumber('salt', salt); + const orderParts = [ {value: exchangeContractAddr, type: SolidityTypes.address}, {value: makerAddr, type: SolidityTypes.address}, {value: takerAddr, type: SolidityTypes.address}, - {value: depositTokenAddr, type: SolidityTypes.address}, - {value: receiveTokenAddr, type: SolidityTypes.address}, + {value: tokenMAddress, type: SolidityTypes.address}, + {value: tokenTAddress, type: SolidityTypes.address}, {value: feeRecipient, type: SolidityTypes.address}, - {value: new BN(depositAmt.toString(), 10), type: SolidityTypes.uint256}, - {value: new BN(receiveAmt.toString(), 10), type: SolidityTypes.uint256}, - {value: new BN(makerFee.toString(), 10), type: SolidityTypes.uint256}, - {value: new BN(takerFee.toString(), 10), type: SolidityTypes.uint256}, - {value: new BN(expiration.toString(), 10), type: SolidityTypes.uint256}, - {value: new BN(salt.toString(), 10), type: SolidityTypes.uint256}, + {value: this.bigNumberToBN(valueM), type: SolidityTypes.uint256}, + {value: this.bigNumberToBN(valueT), type: SolidityTypes.uint256}, + {value: this.bigNumberToBN(makerFee), type: SolidityTypes.uint256}, + {value: this.bigNumberToBN(takerFee), type: SolidityTypes.uint256}, + {value: this.bigNumberToBN(expiration), type: SolidityTypes.uint256}, + {value: this.bigNumberToBN(salt), type: SolidityTypes.uint256}, ]; - const hashBuff = ethABI.soliditySHA3(_.map(orderParts, 'type'), _.map(orderParts, 'value')); - const buffHashHex = ethUtil.bufferToHex(hashBuff); - return buffHashHex; + const types = _.map(orderParts, o => o.type); + const values = _.map(orderParts, o => o.value); + const hashBuff = ethABI.soliditySHA3(types, values); + const hashHex = ethUtil.bufferToHex(hashBuff); + return hashHex; } /** * Verifies that the elliptic curve signature `signature` was generated @@ -123,4 +129,13 @@ export class ZeroEx { const baseUnitAmount = amount.times(unit); return baseUnitAmount; } + + /** + * Converts BigNumber instance to BN + * We do it because ethABI accepts only BN's + * We should be consistent about using BigNumbers in our codebase and not use BN anywhere else + */ + private static bigNumberToBN(value: BigNumber.BigNumber) { + return new BN(value.toString(), 10); + } } diff --git a/src/ts/types.ts b/src/ts/types.ts index c3da8d81e..c49ef2331 100644 --- a/src/ts/types.ts +++ b/src/ts/types.ts @@ -17,5 +17,4 @@ export const SolidityTypes = strEnum([ 'string', 'bool', ]); - export type SolidityTypes = keyof typeof SolidityTypes; diff --git a/test/0x.js.ts b/test/0x.js.ts index 07c58f1a5..d5b2015fb 100644 --- a/test/0x.js.ts +++ b/test/0x.js.ts @@ -11,8 +11,9 @@ const expect = chai.expect; describe('ZeroEx library', () => { describe('#getOrderHash', () => { + const ORDER_HASH = '0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7'; it('defaults takerAddress to NULL address', () => { - const orderHash = ZeroEx.getOrderHash( + const orderHash = ZeroEx.getOrderHashHex( constants.NULL_ADDRESS, constants.NULL_ADDRESS, '', @@ -26,10 +27,10 @@ describe('ZeroEx library', () => { new BigNumber(0), new BigNumber(0), ); - expect(orderHash).to.be.equal('0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7'); + expect(orderHash).to.be.equal(ORDER_HASH); }); it('calculates the order hash', () => { - const orderHash = ZeroEx.getOrderHash( + const orderHash = ZeroEx.getOrderHashHex( constants.NULL_ADDRESS, constants.NULL_ADDRESS, constants.NULL_ADDRESS, @@ -43,7 +44,7 @@ describe('ZeroEx library', () => { new BigNumber(0), new BigNumber(0), ); - expect(orderHash).to.be.equal('0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7'); + expect(orderHash).to.be.equal(ORDER_HASH); }); }); describe('#isValidSignature', () => { -- cgit v1.2.3 From 3195741ebcfc95f4622aebcdc23fc5b8630efe39 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 26 May 2017 12:17:31 +0200 Subject: Address feedback --- src/ts/0x.js.ts | 5 +++-- src/ts/globals.d.ts | 2 +- src/ts/types.ts | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts index 893d95de6..ba922d3db 100644 --- a/src/ts/0x.js.ts +++ b/src/ts/0x.js.ts @@ -132,8 +132,9 @@ export class ZeroEx { /** * Converts BigNumber instance to BN - * We do it because ethABI accepts only BN's - * We should be consistent about using BigNumbers in our codebase and not use BN anywhere else + * The only we convert to BN is to remain compatible with `ethABI. soliditySHA3 ` that + * expects values of Solidity type `uint` to be of type `BN`. + * We do not use BN anywhere else in the codebase. */ private static bigNumberToBN(value: BigNumber.BigNumber) { return new BN(value.toString(), 10); diff --git a/src/ts/globals.d.ts b/src/ts/globals.d.ts index a50c635fb..99baf593f 100644 --- a/src/ts/globals.d.ts +++ b/src/ts/globals.d.ts @@ -26,5 +26,5 @@ declare module 'ethereumjs-util' { } declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any) => Buffer; + const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; } diff --git a/src/ts/types.ts b/src/ts/types.ts index c49ef2331..04902cca6 100644 --- a/src/ts/types.ts +++ b/src/ts/types.ts @@ -13,8 +13,5 @@ function strEnum(values: string[]): {[key: string]: string} { export const SolidityTypes = strEnum([ 'address', 'uint256', - 'uint8', - 'string', - 'bool', ]); export type SolidityTypes = keyof typeof SolidityTypes; -- cgit v1.2.3