diff options
38 files changed, 510 insertions, 401 deletions
diff --git a/.gitignore b/.gitignore index 96f562b1e..73b18e3f4 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ packages/contract-wrappers/src/contract_wrappers/generated/ packages/metacoin/src/contract_wrappers packages/fill-scenarios/src/generated_contract_wrappers/ packages/order-watcher/src/generated_contract_wrappers/ +packages/order-utils/src/generated_contract_wrappers/ packages/migrations/src/v1/contract_wrappers packages/migrations/src/v2/contract_wrappers diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol index f7fcd36b6..af1199f66 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol @@ -30,7 +30,7 @@ contract MixinSignatureValidator is { // Mapping of hash => signer => signed - mapping(bytes32 => mapping(address => bool)) preSigned; + mapping(bytes32 => mapping(address => bool)) public preSigned; /// @dev Approves a hash on-chain using any valid signature type. /// After presigning a hash, the preSign signature type will become valid for that hash and signer. diff --git a/packages/contracts/src/utils/address_utils.ts b/packages/contracts/src/utils/address_utils.ts index dc63459f9..a9fb6921a 100644 --- a/packages/contracts/src/utils/address_utils.ts +++ b/packages/contracts/src/utils/address_utils.ts @@ -1,6 +1,4 @@ -import { generatePseudoRandomSalt } from '@0xproject/order-utils'; - -import { crypto } from './crypto'; +import { crypto, generatePseudoRandomSalt } from '@0xproject/order-utils'; export const addressUtils = { generatePseudoRandomAddress(): string { diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 0446f35d1..f7bd207a4 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -1,4 +1,4 @@ -import { Provider, SignedOrder, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; +import { AssetProxyId, Provider, SignedOrder, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -9,7 +9,7 @@ import { constants } from './constants'; import { formatters } from './formatters'; import { logDecoder } from './log_decoder'; import { orderUtils } from './order_utils'; -import { AssetProxyId, OrderInfo, SignedTransaction } from './types'; +import { OrderInfo, SignedTransaction } from './types'; export class ExchangeWrapper { private _exchange: ExchangeContract; diff --git a/packages/contracts/src/utils/match_order_tester.ts b/packages/contracts/src/utils/match_order_tester.ts index 87399b9f6..85348b14d 100644 --- a/packages/contracts/src/utils/match_order_tester.ts +++ b/packages/contracts/src/utils/match_order_tester.ts @@ -1,5 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; +import { assetProxyUtils, crypto, orderHashUtils } from '@0xproject/order-utils'; +import { AssetProxyId, LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); @@ -14,17 +15,13 @@ import { ExchangeContract, FillContractEventArgs, } from '../contract_wrappers/generated/exchange'; -import { assetProxyUtils } from '../utils/asset_proxy_utils'; import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; -import { crypto } from '../utils/crypto'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; import { - AssetProxyId, ContractName, ERC20BalancesByOwner, ERC721TokenIdsByOwner, @@ -122,7 +119,7 @@ export class MatchOrderTester { const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; // Verify Left order preconditions const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrderLeft), + orderHashUtils.getOrderHashHex(signedOrderLeft), ); const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft ? initialTakerAssetFilledAmountLeft @@ -130,7 +127,7 @@ export class MatchOrderTester { expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderTakerAssetFilledAmountLeft); // Verify Right order preconditions const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrderRight), + orderHashUtils.getOrderHashHex(signedOrderRight), ); const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight ? initialTakerAssetFilledAmountRight @@ -181,7 +178,7 @@ export class MatchOrderTester { orderTakerAssetFilledAmountRight: BigNumber, ): Promise<TransferAmounts> { let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrderLeft), + orderHashUtils.getOrderHashHex(signedOrderLeft), ); amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderTakerAssetFilledAmountLeft); const amountSoldByLeftMaker = amountBoughtByLeftMaker @@ -192,7 +189,7 @@ export class MatchOrderTester { .dividedToIntegerBy(signedOrderRight.makerAssetAmount); const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountReceivedByRightMaker); let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrderRight), + orderHashUtils.getOrderHashHex(signedOrderRight), ); amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderTakerAssetFilledAmountRight); const amountSoldByRightMaker = amountBoughtByRightMaker diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts index f704c26ec..59fc663f4 100644 --- a/packages/contracts/src/utils/order_factory.ts +++ b/packages/contracts/src/utils/order_factory.ts @@ -1,22 +1,21 @@ import { generatePseudoRandomSalt } from '@0xproject/order-utils'; -import { SignedOrder, UnsignedOrder } from '@0xproject/types'; +import { orderHashUtils } from '@0xproject/order-utils'; +import { Order, SignatureType, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import { constants } from './constants'; -import { orderUtils } from './order_utils'; import { signingUtils } from './signing_utils'; -import { SignatureType } from './types'; export class OrderFactory { - private _defaultOrderParams: Partial<UnsignedOrder>; + private _defaultOrderParams: Partial<Order>; private _privateKey: Buffer; - constructor(privateKey: Buffer, defaultOrderParams: Partial<UnsignedOrder>) { + constructor(privateKey: Buffer, defaultOrderParams: Partial<Order>) { this._defaultOrderParams = defaultOrderParams; this._privateKey = privateKey; } public newSignedOrder( - customOrderParams: Partial<UnsignedOrder> = {}, + customOrderParams: Partial<Order> = {}, signatureType: SignatureType = SignatureType.Ecrecover, ): SignedOrder { const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); @@ -27,8 +26,8 @@ export class OrderFactory { takerAddress: constants.NULL_ADDRESS, ...this._defaultOrderParams, ...customOrderParams, - } as any) as UnsignedOrder; - const orderHashBuff = orderUtils.getOrderHashBuff(order); + } as any) as Order; + const orderHashBuff = orderHashUtils.getOrderHashBuff(order); const signature = signingUtils.signMessage(orderHashBuff, this._privateKey, signatureType); const signedOrder = { ...order, diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 6d1aaa06b..dd7a04cb6 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -1,8 +1,7 @@ -import { Order, SignedOrder, UnsignedOrder } from '@0xproject/types'; +import { Order, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import ethUtil = require('ethereumjs-util'); -import { crypto } from './crypto'; import { CancelOrder, MatchOrder } from './types'; export const orderUtils = { @@ -21,6 +20,9 @@ export const orderUtils = { }; return cancel; }, + // TODO: This seems redundant... it currently returns a deep copy w/o signature. + // Question: Should we still have a separate OrderStruct type that simply doesn't + // include the exchangeAddress? Seems like we need to for batch ops... getOrderStruct(signedOrder: SignedOrder): Order { const orderStruct = { senderAddress: signedOrder.senderAddress, @@ -35,74 +37,10 @@ export const orderUtils = { salt: signedOrder.salt, makerAssetData: signedOrder.makerAssetData, takerAssetData: signedOrder.takerAssetData, + exchangeAddress: signedOrder.exchangeAddress, }; return orderStruct; }, - getDomainSeparatorSchemaHex(): string { - const domainSeparatorSchemaHashBuff = crypto.solSHA3(['DomainSeparator(address contract)']); - const schemaHashHex = `0x${domainSeparatorSchemaHashBuff.toString('hex')}`; - return schemaHashHex; - }, - getDomainSeparatorHashHex(exchangeAddress: string): string { - const domainSeparatorHashBuff = crypto.solSHA3([exchangeAddress]); - const domainSeparatorHashHex = `0x${domainSeparatorHashBuff.toString('hex')}`; - return domainSeparatorHashHex; - }, - getOrderSchemaHex(): string { - const orderSchemaHashBuff = crypto.solSHA3([ - 'Order(', - 'address makerAddress,', - 'address takerAddress,', - 'address feeRecipientAddress,', - 'address senderAddress,', - 'uint256 makerAssetAmount,', - 'uint256 takerAssetAmount,', - 'uint256 makerFee,', - 'uint256 takerFee,', - 'uint256 expirationTimeSeconds,', - 'uint256 salt,', - 'bytes makerAssetData,', - 'bytes takerAssetData,', - ')', - ]); - const schemaHashHex = `0x${orderSchemaHashBuff.toString('hex')}`; - return schemaHashHex; - }, - getOrderHashBuff(order: SignedOrder | UnsignedOrder): Buffer { - const makerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.makerAssetData)]); - const takerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.takerAssetData)]); - - const orderParamsHashBuff = crypto.solSHA3([ - order.makerAddress, - order.takerAddress, - order.feeRecipientAddress, - order.senderAddress, - order.makerAssetAmount, - order.takerAssetAmount, - order.makerFee, - order.takerFee, - order.expirationTimeSeconds, - order.salt, - makerAssetDataHash, - takerAssetDataHash, - ]); - const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`; - const orderSchemaHashHex = orderUtils.getOrderSchemaHex(); - const domainSeparatorHashHex = this.getDomainSeparatorHashHex(order.exchangeAddress); - const domainSeparatorSchemaHex = this.getDomainSeparatorSchemaHex(); - const orderHashBuff = crypto.solSHA3([ - new BigNumber(domainSeparatorSchemaHex), - new BigNumber(domainSeparatorHashHex), - new BigNumber(orderSchemaHashHex), - new BigNumber(orderParamsHashHex), - ]); - return orderHashBuff; - }, - getOrderHashHex(order: SignedOrder | UnsignedOrder): string { - const orderHashBuff = orderUtils.getOrderHashBuff(order); - const orderHashHex = `0x${orderHashBuff.toString('hex')}`; - return orderHashHex; - }, createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder): MatchOrder { const fill = { left: orderUtils.getOrderStruct(signedOrderLeft), diff --git a/packages/contracts/src/utils/signing_utils.ts b/packages/contracts/src/utils/signing_utils.ts index 61ab1f138..41d63832e 100644 --- a/packages/contracts/src/utils/signing_utils.ts +++ b/packages/contracts/src/utils/signing_utils.ts @@ -1,7 +1,6 @@ +import { SignatureType } from '@0xproject/types'; import * as ethUtil from 'ethereumjs-util'; -import { SignatureType } from './types'; - export const signingUtils = { signMessage(message: Buffer, privateKey: Buffer, signatureType: SignatureType): Buffer { if (signatureType === SignatureType.Ecrecover) { diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts index 941bff96d..41907132a 100644 --- a/packages/contracts/src/utils/transaction_factory.ts +++ b/packages/contracts/src/utils/transaction_factory.ts @@ -1,10 +1,10 @@ -import { generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { crypto, generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { SignatureType } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as ethUtil from 'ethereumjs-util'; -import { crypto } from './crypto'; import { signingUtils } from './signing_utils'; -import { SignatureType, SignedTransaction } from './types'; +import { SignedTransaction } from './types'; export class TransactionFactory { private _signer: string; diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 90f90ec27..e4401bd92 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -43,12 +43,6 @@ export interface CancelOrdersBefore { salt: BigNumber; } -export enum AssetProxyId { - INVALID, - ERC20, - ERC721, -} - export interface TransactionDataParams { name: string; abi: AbiDefinition[]; @@ -113,16 +107,6 @@ export enum ContractName { Authorizable = 'Authorizable', } -export enum SignatureType { - Illegal, - Invalid, - Caller, - Ecrecover, - EIP712, - Trezor, - Contract, -} - export interface SignedTransaction { exchangeAddress: string; salt: BigNumber; @@ -158,23 +142,6 @@ export interface OrderInfo { orderTakerAssetFilledAmount: BigNumber; } -export interface ERC20ProxyData { - assetProxyId: AssetProxyId; - tokenAddress: string; -} - -export interface ERC721ProxyData { - assetProxyId: AssetProxyId; - tokenAddress: string; - tokenId: BigNumber; -} - -export interface ProxyData { - assetProxyId: AssetProxyId; - tokenAddress?: string; - data?: any; -} - export interface CancelOrder { order: Order; takerAssetCancelAmount: BigNumber; diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 9bcdfa2b8..faab39759 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -1,4 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { assetProxyUtils } from '@0xproject/order-utils'; +import { AssetProxyId } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -8,12 +10,10 @@ import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/d import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; -import { AssetProxyId } from '../../src/utils/types'; import { provider, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index be8d14cb0..e8397f8a2 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -1,5 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; +import { assetProxyUtils, crypto, orderHashUtils } from '@0xproject/order-utils'; +import { AssetProxyId, LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -17,16 +18,13 @@ import { FillContractEventArgs, } from '../../src/contract_wrappers/generated/exchange'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; -import { crypto } from '../../src/utils/crypto'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; -import { orderUtils } from '../../src/utils/order_utils'; -import { AssetProxyId, ContractName, ERC20BalancesByOwner, ExchangeStatus } from '../../src/utils/types'; +import { ContractName, ERC20BalancesByOwner, ExchangeStatus } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); @@ -127,7 +125,6 @@ describe('Exchange core', () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); @@ -141,7 +138,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); @@ -151,7 +148,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountAfter1 = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountAfter1).to.be.bignumber.equal(fillTakerAssetAmount1); @@ -161,7 +158,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountAfter2 = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountAfter2).to.be.bignumber.equal(takerAssetFilledAmountAfter1); }); @@ -173,7 +170,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); @@ -181,7 +178,7 @@ describe('Exchange core', () => { await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount); @@ -226,7 +223,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); @@ -234,7 +231,7 @@ describe('Exchange core', () => { await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount); @@ -279,7 +276,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); @@ -287,7 +284,7 @@ describe('Exchange core', () => { await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount); @@ -333,7 +330,7 @@ describe('Exchange core', () => { }); const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); @@ -341,7 +338,7 @@ describe('Exchange core', () => { await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderUtils.getOrderHashHex(signedOrder), + orderHashUtils.getOrderHashHex(signedOrder), ); const expectedMakerAmountBoughtAfter = takerAssetFillAmount.add(takerAssetFilledAmountBefore); expect(makerAmountBoughtAfter).to.be.bignumber.equal(expectedMakerAmountBoughtAfter); @@ -441,7 +438,7 @@ describe('Exchange core', () => { expect(expectedFilledTakerAssetAmount).to.be.bignumber.equal(logArgs.takerAssetFilledAmount); expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.makerFeePaid); expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.takerFeePaid); - expect(orderUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); + expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); }); it('should throw when taker is specified and order is claimed by other', async () => { @@ -534,12 +531,6 @@ describe('Exchange core', () => { await expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( constants.REVERT, ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); }); it('should throw if taker allowances are too low to fill order', async () => { @@ -555,12 +546,6 @@ describe('Exchange core', () => { await expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( constants.REVERT, ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); }); it('should not change erc20Balances if an order is expired', async () => { @@ -651,7 +636,7 @@ describe('Exchange core', () => { expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); - expect(orderUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); + expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); }); it('should log an error if already cancelled', async () => { diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts index b9c7039bd..8bc66e3cf 100644 --- a/packages/contracts/test/exchange/dispatcher.ts +++ b/packages/contracts/test/exchange/dispatcher.ts @@ -1,4 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { assetProxyUtils } from '@0xproject/order-utils'; +import { AssetProxyId } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as Web3 from 'web3'; @@ -8,12 +10,10 @@ import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c2 import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; import { TestAssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/test_asset_proxy_dispatcher'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; -import { AssetProxyId } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts index bbca54274..5f2c24ac0 100644 --- a/packages/contracts/test/exchange/libs.ts +++ b/packages/contracts/test/exchange/libs.ts @@ -1,4 +1,5 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils'; import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; @@ -7,11 +8,9 @@ import ethUtil = require('ethereumjs-util'); import { TestLibsContract } from '../../src/contract_wrappers/generated/test_libs'; import { addressUtils } from '../../src/utils/address_utils'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { OrderFactory } from '../../src/utils/order_factory'; -import { orderUtils } from '../../src/utils/order_utils'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); @@ -59,19 +58,19 @@ describe('Exchange libs', () => { describe('getOrderSchema', () => { it('should output the correct order schema hash', async () => { const orderSchema = await libs.getOrderSchemaHash.callAsync(); - expect(orderUtils.getOrderSchemaHex()).to.be.equal(orderSchema); + expect(orderHashUtils._getOrderSchemaHex()).to.be.equal(orderSchema); }); }); describe('getDomainSeparatorSchema', () => { it('should output the correct domain separator schema hash', async () => { const domainSeparatorSchema = await libs.getDomainSeparatorSchemaHash.callAsync(); - expect(orderUtils.getDomainSeparatorSchemaHex()).to.be.equal(domainSeparatorSchema); + expect(orderHashUtils._getDomainSeparatorSchemaHex()).to.be.equal(domainSeparatorSchema); }); }); describe('getOrderHash', () => { it('should output the correct order hash', async () => { const orderHashHex = await libs.publicGetOrderHash.callAsync(signedOrder); - expect(orderUtils.getOrderHashHex(signedOrder)).to.be.equal(orderHashHex); + expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(orderHashHex); }); }); }); diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 0da0287bc..67281a915 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -1,5 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; +import { assetProxyUtils, crypto } from '@0xproject/order-utils'; +import { AssetProxyId, LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -17,17 +18,14 @@ import { FillContractEventArgs, } from '../../src/contract_wrappers/generated/exchange'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; -import { crypto } from '../../src/utils/crypto'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; +import { MatchOrderTester } from '../../src/utils/match_order_tester'; import { OrderFactory } from '../../src/utils/order_factory'; -import { orderUtils } from '../../src/utils/order_utils'; import { - AssetProxyId, ContractName, ERC20BalancesByOwner, ERC721TokenIdsByOwner, @@ -36,8 +34,6 @@ import { } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; -import { MatchOrderTester } from '../../src/utils/match_order_tester'; - chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts index 376fff438..a41a19426 100644 --- a/packages/contracts/test/exchange/signature_validator.ts +++ b/packages/contracts/test/exchange/signature_validator.ts @@ -1,4 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { orderHashUtils } from '@0xproject/order-utils'; +import { assetProxyUtils } from '@0xproject/order-utils'; import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; @@ -7,11 +9,9 @@ import ethUtil = require('ethereumjs-util'); import { TestSignatureValidatorContract } from '../../src/contract_wrappers/generated/test_signature_validator'; import { addressUtils } from '../../src/utils/address_utils'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { OrderFactory } from '../../src/utils/order_factory'; -import { orderUtils } from '../../src/utils/order_utils'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); @@ -65,7 +65,7 @@ describe('MixinSignatureValidator', () => { }); it('should return true with a valid signature', async () => { - const orderHashHex = orderUtils.getOrderHashHex(signedOrder); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, @@ -84,7 +84,7 @@ describe('MixinSignatureValidator', () => { ]); const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; signedOrder.signature = invalidSigHex; - const orderHashHex = orderUtils.getOrderHashHex(signedOrder); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts index 33fe11bfa..3f884a6c3 100644 --- a/packages/contracts/test/exchange/transactions.ts +++ b/packages/contracts/test/exchange/transactions.ts @@ -1,5 +1,5 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { Order, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, Order, SignatureType, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; @@ -9,7 +9,7 @@ import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/d import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; +import { assetProxyUtils } from '@0xproject/order-utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -17,13 +17,7 @@ import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; import { orderUtils } from '../../src/utils/order_utils'; import { TransactionFactory } from '../../src/utils/transaction_factory'; -import { - AssetProxyId, - ERC20BalancesByOwner, - ExchangeStatus, - SignatureType, - SignedTransaction, -} from '../../src/utils/types'; +import { ERC20BalancesByOwner, ExchangeStatus, SignedTransaction } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index 7e1818f4a..d8055908a 100644 --- a/packages/contracts/test/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -1,5 +1,5 @@ import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { SignedOrder } from '@0xproject/types'; +import { AssetProxyId, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -14,14 +14,14 @@ import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry'; import { artifacts } from '../../src/utils/artifacts'; -import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; +import { assetProxyUtils } from '@0xproject/order-utils'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; -import { AssetProxyId, ERC20BalancesByOwner } from '../../src/utils/types'; +import { ERC20BalancesByOwner } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts index 968bac300..de4011853 100644 --- a/packages/contracts/test/libraries/lib_bytes.ts +++ b/packages/contracts/test/libraries/lib_bytes.ts @@ -1,5 +1,5 @@ import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; +import { AssetProxyId, LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import BN = require('bn.js'); @@ -11,7 +11,6 @@ import { TestLibBytesContract } from '../../src/contract_wrappers/generated/test import { artifacts } from '../../src/utils/artifacts'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; -import { AssetProxyId } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); diff --git a/packages/json-schemas/schemas/order_schemas.ts b/packages/json-schemas/schemas/order_schemas.ts index 4dc458d8d..dcbfde6e0 100644 --- a/packages/json-schemas/schemas/order_schemas.ts +++ b/packages/json-schemas/schemas/order_schemas.ts @@ -11,6 +11,7 @@ export const orderSchema = { makerAssetData: { $ref: '/Hex' }, takerAssetData: { $ref: '/Hex' }, salt: { $ref: '/Number' }, + exchangeAddress: { $ref: '/Address' }, feeRecipientAddress: { $ref: '/Address' }, expirationTimeSeconds: { $ref: '/Number' }, }, @@ -25,6 +26,7 @@ export const orderSchema = { 'makerAssetData', 'takerAssetData', 'salt', + 'exchangeAddress', 'feeRecipientAddress', 'expirationTimeSeconds', ], diff --git a/packages/json-schemas/test/schema_test.ts b/packages/json-schemas/test/schema_test.ts index 93e9624a9..3858c7fa7 100644 --- a/packages/json-schemas/test/schema_test.ts +++ b/packages/json-schemas/test/schema_test.ts @@ -180,6 +180,7 @@ describe('Schema', () => { takerAssetData: NULL_ADDRESS, salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500', feeRecipientAddress: NULL_ADDRESS, + exchangeAddress: NULL_ADDRESS, expirationTimeSeconds: '42', }; describe('#orderSchema', () => { diff --git a/packages/migrations/compiler.json b/packages/migrations/compiler.json index e1204b2d1..782b939dc 100644 --- a/packages/migrations/compiler.json +++ b/packages/migrations/compiler.json @@ -1,5 +1,5 @@ { - "artifactsDir": "artifacts/1.0.0", + "artifactsDir": "artifacts/2.0.0", "contractsDir": "../contracts/src/contracts", "contracts": [ "Exchange_v1", @@ -10,7 +10,8 @@ "MultiSigWallet", "MultiSigWalletWithTimeLock", "MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress", - "TokenRegistry" + "TokenRegistry", + "ISigner" ], "compilerSettings": { "outputSelection": { diff --git a/packages/migrations/package.json b/packages/migrations/package.json index 54866110e..ebef2dfb4 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -30,7 +30,7 @@ "v1": "artifacts/1.0.0/@(DummyERC20Token|TokenTransferProxy_v1|Exchange_v1|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|WETH9).json", "v2": - "artifacts/2.0.0/@(DummyERC20Token|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|ZRXToken|WETH9).json" + "artifacts/2.0.0/@(DummyERC20Token|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|ZRXToken|WETH9|ISigner).json" } }, "license": "Apache-2.0", diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index c69d99c05..0b0686954 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -9,13 +9,16 @@ "types": "lib/src/index.d.ts", "scripts": { "watch": "tsc -w", + "prebuild": "run-s clean update_artifacts generate_contract_wrappers", "build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", - "test": "run-s clean build run_mocha", + "generate_contract_wrappers": "abi-gen --abis 'lib/src/artifacts/@(Exchange|ISigner).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers && prettier --write 'src/generated_contract_wrappers/**.ts'", + "update_artifacts": "for i in ${npm_package_config_contracts}; do copyfiles -u 4 ../migrations/artifacts/2.0.0/$i.json lib/src/artifacts; done;", + "test": "run-s build run_mocha", "test:circleci": "yarn test:coverage", "run_mocha": "mocha lib/test/**/*_test.js --bail --exit", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "clean": "shx rm -rf lib scripts", + "clean": "shx rm -rf lib scripts lib/src/artifacts src/generated_contract_wrappers", "lint": "tslint --project .", "manual:postpublish": "yarn build; node ./scripts/postpublish.js", "docs:stage": "node scripts/stage_docs.js", @@ -23,6 +26,7 @@ "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" }, "config": { + "contracts": "ISigner Exchange", "postpublish": { "docPublishConfigs": { "extraFileIncludes": [ @@ -63,8 +67,10 @@ }, "dependencies": { "@0xproject/assert": "^0.2.10", - "@0xproject/json-schemas": "0.7.22", - "@0xproject/types": "0.7.0", + "@0xproject/base-contract": "^0.3.2", + "@0xproject/json-schemas": "0.7.24", + "@0xproject/sol-compiler": "^0.5.0", + "@0xproject/types": "^0.7.1", "@0xproject/typescript-typings": "^0.3.2", "@0xproject/utils": "^0.6.2", "@0xproject/web3-wrapper": "^0.6.4", @@ -72,6 +78,7 @@ "bn.js": "^4.11.8", "ethereumjs-abi": "^0.6.4", "ethereumjs-util": "^5.1.1", + "ethers": "^3.0.15", "lodash": "^4.17.4" }, "publishConfig": { diff --git a/packages/order-utils/src/artifacts.ts b/packages/order-utils/src/artifacts.ts new file mode 100644 index 000000000..6eb5ad0c0 --- /dev/null +++ b/packages/order-utils/src/artifacts.ts @@ -0,0 +1,8 @@ +import { Artifact } from '@0xproject/types'; + +import * as Exchange from './artifacts/Exchange.json'; +import * as ISigner from './artifacts/ISigner.json'; +export const artifacts = { + Exchange: (Exchange as any) as Artifact, + ISigner: (ISigner as any) as Artifact, +}; diff --git a/packages/order-utils/src/assert.ts b/packages/order-utils/src/assert.ts index 5ac402e7e..07cde453a 100644 --- a/packages/order-utils/src/assert.ts +++ b/packages/order-utils/src/assert.ts @@ -8,8 +8,6 @@ import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; -import { isValidSignature } from './signature_utils'; - export const assert = { ...sharedAssert, async isSenderAddressAsync( diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts index c042da5d0..11f81c656 100644 --- a/packages/contracts/src/utils/asset_proxy_utils.ts +++ b/packages/order-utils/src/asset_proxy_utils.ts @@ -1,8 +1,10 @@ +import { AssetProxyId, ERC20ProxyData, ERC721ProxyData, ProxyData } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import ethUtil = require('ethereumjs-util'); -import { AssetProxyId, ERC20ProxyData, ERC721ProxyData, ProxyData } from './types'; +const ERC20_PROXY_METADATA_BYTE_LENGTH = 21; +const ERC721_PROXY_METADATA_BYTE_LENGTH = 53; export const assetProxyUtils = { encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer { @@ -26,8 +28,10 @@ export const assetProxyUtils = { return address; }, encodeUint256(value: BigNumber): Buffer { - const formattedValue = new BN(value.toString(10)); + const base = 10; + const formattedValue = new BN(value.toString(base)); const encodedValue = ethUtil.toBuffer(formattedValue); + // tslint:disable-next-line:custom-no-magic-numbers const paddedValue = ethUtil.setLengthLeft(encodedValue, 32); return paddedValue; }, @@ -45,7 +49,7 @@ export const assetProxyUtils = { }, decodeERC20ProxyData(proxyData: string): ERC20ProxyData { const encodedProxyMetadata = ethUtil.toBuffer(proxyData); - if (encodedProxyMetadata.byteLength !== 21) { + if (encodedProxyMetadata.byteLength !== ERC20_PROXY_METADATA_BYTE_LENGTH) { throw new Error( `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${ encodedProxyMetadata.byteLength @@ -61,7 +65,7 @@ export const assetProxyUtils = { }), but got ${assetProxyId}`, ); } - const encodedTokenAddress = encodedProxyMetadata.slice(1, 21); + const encodedTokenAddress = encodedProxyMetadata.slice(1, ERC20_PROXY_METADATA_BYTE_LENGTH); const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); const erc20ProxyData = { assetProxyId, @@ -79,7 +83,7 @@ export const assetProxyUtils = { }, decodeERC721ProxyData(proxyData: string): ERC721ProxyData { const encodedProxyMetadata = ethUtil.toBuffer(proxyData); - if (encodedProxyMetadata.byteLength !== 53) { + if (encodedProxyMetadata.byteLength !== ERC721_PROXY_METADATA_BYTE_LENGTH) { throw new Error( `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 53. Got ${ encodedProxyMetadata.byteLength @@ -95,9 +99,10 @@ export const assetProxyUtils = { }), but got ${assetProxyId}`, ); } - const encodedTokenAddress = encodedProxyMetadata.slice(1, 21); + const addressOffset = 21; + const encodedTokenAddress = encodedProxyMetadata.slice(1, addressOffset); const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); - const encodedTokenId = encodedProxyMetadata.slice(21, 53); + const encodedTokenId = encodedProxyMetadata.slice(addressOffset, ERC721_PROXY_METADATA_BYTE_LENGTH); const tokenId = assetProxyUtils.decodeUint256(encodedTokenId); const erc721ProxyData = { assetProxyId, diff --git a/packages/contracts/src/utils/crypto.ts b/packages/order-utils/src/crypto.ts index 80c5f30a5..517ca2840 100644 --- a/packages/contracts/src/utils/crypto.ts +++ b/packages/order-utils/src/crypto.ts @@ -26,7 +26,8 @@ export const crypto = { argTypes.push('uint8'); } else if (arg.isBigNumber) { argTypes.push('uint256'); - args[i] = new BN(arg.toString(10), 10); + const base = 10; + args[i] = new BN(arg.toString(base), base); } else if (ethUtil.isValidAddress(arg)) { argTypes.push('address'); } else if (_.isString(arg)) { diff --git a/packages/order-utils/src/formatters.ts b/packages/order-utils/src/formatters.ts deleted file mode 100644 index 2b6f4ddb7..000000000 --- a/packages/order-utils/src/formatters.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Order, OrderAddresses, OrderValues } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -export const formatters = { - getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] { - const orderAddresses: OrderAddresses = [ - order.maker, - order.taker, - order.makerTokenAddress, - order.takerTokenAddress, - order.feeRecipient, - ]; - const orderValues: OrderValues = [ - order.makerTokenAmount, - order.takerTokenAmount, - order.makerFee, - order.takerFee, - order.expirationUnixTimestampSec, - order.salt, - ]; - return [orderAddresses, orderValues]; - }, -}; diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index e9cea95ed..5b4e20119 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -1,11 +1,12 @@ -export { getOrderHashHex, isValidOrderHash } from './order_hash'; -export { isValidSignature, signOrderHashAsync } from './signature_utils'; +export { orderHashUtils } from './order_hash'; +export { isValidSignatureAsync, ecSignOrderHashAsync } from './signature_utils'; export { orderFactory } from './order_factory'; export { constants } from './constants'; +export { crypto } from './crypto'; export { generatePseudoRandomSalt } from './salt'; export { OrderError } from './types'; -export { formatters } from './formatters'; export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; export { RemainingFillableCalculator } from './remaining_fillable_calculator'; export { OrderStateUtils } from './order_state_utils'; +export { assetProxyUtils } from './asset_proxy_utils'; diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 2759aac81..fe341b845 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -2,48 +2,56 @@ import { Provider, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import { getOrderHashHex } from './order_hash'; +import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; -import { signOrderHashAsync } from './signature_utils'; +import { ecSignOrderHashAsync, getVRSHexString } from './signature_utils'; const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; export const orderFactory = { async createSignedOrderAsync( provider: Provider, - maker: string, - taker: string, + makerAddress: string, + takerAddress: string, + senderAddress: string, makerFee: BigNumber, takerFee: BigNumber, - makerTokenAmount: BigNumber, - makerTokenAddress: string, - takerTokenAmount: BigNumber, - takerTokenAddress: string, - exchangeContractAddress: string, - feeRecipient: string, - expirationUnixTimestampSecIfExists?: BigNumber, + makerAssetAmount: BigNumber, + makerAssetData: string, + takerAssetAmount: BigNumber, + takerAssetData: string, + exchangeAddress: string, + feeRecipientAddress: string, + expirationTimeSecondsIfExists?: BigNumber, ): Promise<SignedOrder> { const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite - const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists) + const expirationTimeSeconds = _.isUndefined(expirationTimeSecondsIfExists) ? defaultExpirationUnixTimestampSec - : expirationUnixTimestampSecIfExists; + : expirationTimeSecondsIfExists; const order = { - maker, - taker, + makerAddress, + takerAddress, + senderAddress, makerFee, takerFee, - makerTokenAmount, - takerTokenAmount, - makerTokenAddress, - takerTokenAddress, + makerAssetAmount, + takerAssetAmount, + makerAssetData, + takerAssetData, salt: generatePseudoRandomSalt(), - exchangeContractAddress, - feeRecipient, - expirationUnixTimestampSec, + exchangeAddress, + feeRecipientAddress, + expirationTimeSeconds, }; - const orderHash = getOrderHashHex(order); - const ecSignature = await signOrderHashAsync(provider, orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX); - const signedOrder: SignedOrder = _.assign(order, { ecSignature }); + const orderHash = orderHashUtils.getOrderHashHex(order); + const ecSignature = await ecSignOrderHashAsync( + provider, + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + const signature = getVRSHexString(ecSignature); + const signedOrder: SignedOrder = _.assign(order, { signature }); return signedOrder; }, }; diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts index 108344a04..3d0db5b0c 100644 --- a/packages/order-utils/src/order_hash.ts +++ b/packages/order-utils/src/order_hash.ts @@ -1,5 +1,5 @@ import { schemas, SchemaValidator } from '@0xproject/json-schemas'; -import { Order, SignedOrder, SolidityTypes } from '@0xproject/types'; +import { Order, SignatureType, SignedOrder, SolidityTypes } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import * as ethABI from 'ethereumjs-abi'; @@ -7,84 +7,110 @@ import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { assert } from './assert'; +import { crypto } from './crypto'; const INVALID_TAKER_FORMAT = 'instance.taker is not of a type(s) string'; -/** - * Converts BigNumber instance to BN - * The only reason we convert to BN is to remain compatible with `ethABI.soliditySHA3` that - * expects values of Solidity type `uint` to be passed as type `BN`. - * We do not use BN anywhere else in the codebase. - */ -function bigNumberToBN(value: BigNumber): BN { - const base = 10; - return new BN(value.toString(), base); -} - -/** - * Computes the orderHash for a supplied order. - * @param order An object that conforms to the Order or SignedOrder interface definitions. - * @return The resulting orderHash from hashing the supplied order. - */ -export function getOrderHashHex(order: Order | SignedOrder): string { - try { - assert.doesConformToSchema('order', order, schemas.orderSchema); - } catch (error) { - if (_.includes(error.message, INVALID_TAKER_FORMAT)) { - const errMsg = - 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; - throw new Error(errMsg); +export const orderHashUtils = { + /** + * Checks if the supplied hex encoded order hash is valid. + * Note: Valid means it has the expected format, not that an order with the orderHash exists. + * Use this method when processing orderHashes submitted as user input. + * @param orderHash Hex encoded orderHash. + * @return Whether the supplied orderHash has the expected format. + */ + isValidOrderHash(orderHash: string): boolean { + // Since this method can be called to check if any arbitrary string conforms to an orderHash's + // format, we only assert that we were indeed passed a string. + assert.isString('orderHash', orderHash); + const schemaValidator = new SchemaValidator(); + const isValid = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid; + return isValid; + }, + /** + * Computes the orderHash for a supplied order. + * @param order An object that conforms to the Order or SignedOrder interface definitions. + * @return The resulting orderHash from hashing the supplied order. + */ + getOrderHashHex(order: SignedOrder | Order): string { + try { + assert.doesConformToSchema('order', order, schemas.orderSchema); + } catch (error) { + if (_.includes(error.message, INVALID_TAKER_FORMAT)) { + const errMsg = + 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; + throw new Error(errMsg); + } + throw error; } - throw error; - } - const orderParts = [ - { value: order.exchangeContractAddress, type: SolidityTypes.Address }, - { 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: bigNumberToBN(order.makerTokenAmount), - type: SolidityTypes.Uint256, - }, - { - value: bigNumberToBN(order.takerTokenAmount), - type: SolidityTypes.Uint256, - }, - { - value: bigNumberToBN(order.makerFee), - type: SolidityTypes.Uint256, - }, - { - value: bigNumberToBN(order.takerFee), - type: SolidityTypes.Uint256, - }, - { - value: bigNumberToBN(order.expirationUnixTimestampSec), - type: SolidityTypes.Uint256, - }, - { value: bigNumberToBN(order.salt), type: SolidityTypes.Uint256 }, - ]; - 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; -} -/** - * Checks if the supplied hex encoded order hash is valid. - * Note: Valid means it has the expected format, not that an order with the orderHash exists. - * Use this method when processing orderHashes submitted as user input. - * @param orderHash Hex encoded orderHash. - * @return Whether the supplied orderHash has the expected format. - */ -export function isValidOrderHash(orderHash: string): boolean { - // Since this method can be called to check if any arbitrary string conforms to an orderHash's - // format, we only assert that we were indeed passed a string. - assert.isString('orderHash', orderHash); - const schemaValidator = new SchemaValidator(); - const isValid = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid; - return isValid; -} + const orderHashBuff = this.getOrderHashBuff(order); + const orderHashHex = `0x${orderHashBuff.toString('hex')}`; + return orderHashHex; + }, + /** + * Computes the orderHash for a supplied order and returns it as a Buffer + * @param order An object that conforms to the Order or SignedOrder interface definitions. + * @return The resulting orderHash from hashing the supplied order as a Buffer + */ + getOrderHashBuff(order: SignedOrder | Order): Buffer { + const makerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.makerAssetData)]); + const takerAssetDataHash = crypto.solSHA3([ethUtil.toBuffer(order.takerAssetData)]); + + const orderParamsHashBuff = crypto.solSHA3([ + order.makerAddress, + order.takerAddress, + order.feeRecipientAddress, + order.senderAddress, + order.makerAssetAmount, + order.takerAssetAmount, + order.makerFee, + order.takerFee, + order.expirationTimeSeconds, + order.salt, + makerAssetDataHash, + takerAssetDataHash, + ]); + const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`; + const orderSchemaHashHex = this._getOrderSchemaHex(); + const domainSeparatorHashHex = this._getDomainSeparatorHashHex(order.exchangeAddress); + const domainSeparatorSchemaHex = this._getDomainSeparatorSchemaHex(); + const orderHashBuff = crypto.solSHA3([ + new BigNumber(domainSeparatorSchemaHex), + new BigNumber(domainSeparatorHashHex), + new BigNumber(orderSchemaHashHex), + new BigNumber(orderParamsHashHex), + ]); + return orderHashBuff; + }, + _getOrderSchemaHex(): string { + const orderSchemaHashBuff = crypto.solSHA3([ + 'Order(', + 'address makerAddress,', + 'address takerAddress,', + 'address feeRecipientAddress,', + 'address senderAddress,', + 'uint256 makerAssetAmount,', + 'uint256 takerAssetAmount,', + 'uint256 makerFee,', + 'uint256 takerFee,', + 'uint256 expirationTimeSeconds,', + 'uint256 salt,', + 'bytes makerAssetData,', + 'bytes takerAssetData,', + ')', + ]); + const schemaHashHex = `0x${orderSchemaHashBuff.toString('hex')}`; + return schemaHashHex; + }, + _getDomainSeparatorSchemaHex(): string { + const domainSeparatorSchemaHashBuff = crypto.solSHA3(['DomainSeparator(address contract)']); + const schemaHashHex = `0x${domainSeparatorSchemaHashBuff.toString('hex')}`; + return schemaHashHex; + }, + _getDomainSeparatorHashHex(exchangeAddress: string): string { + const domainSeparatorHashBuff = crypto.solSHA3([exchangeAddress]); + const domainSeparatorHashHex = `0x${domainSeparatorHashBuff.toString('hex')}`; + return domainSeparatorHashHex; + }, +}; diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts index 36171f526..61050c9d6 100644 --- a/packages/order-utils/src/order_state_utils.ts +++ b/packages/order-utils/src/order_state_utils.ts @@ -11,7 +11,8 @@ import * as _ from 'lodash'; import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; -import { getOrderHashHex } from './order_hash'; +import { assetProxyUtils } from './asset_proxy_utils'; +import { orderHashUtils } from './order_hash'; import { RemainingFillableCalculator } from './remaining_fillable_calculator'; const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001; @@ -23,7 +24,7 @@ export class OrderStateUtils { const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add( orderRelevantState.filledTakerTokenAmount, ); - const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + const availableTakerTokenAmount = signedOrder.takerAssetAmount.minus(unavailableTakerTokenAmount); if (availableTakerTokenAmount.eq(0)) { throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); } @@ -42,9 +43,9 @@ export class OrderStateUtils { throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance); } } - const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount + const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) - .dividedBy(signedOrder.makerTokenAmount); + .dividedBy(signedOrder.makerAssetAmount); if ( orderRelevantState.remainingFillableTakerTokenAmount.lessThan( minFillableTakerTokenAmountWithinNoRoundingErrorRange, @@ -62,7 +63,7 @@ export class OrderStateUtils { } public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder); - const orderHash = getOrderHashHex(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); try { OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState); const orderState: OrderStateValid = { @@ -82,22 +83,22 @@ export class OrderStateUtils { } public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { const zrxTokenAddress = this._orderFilledCancelledFetcher.getZRXTokenAddress(); - const orderHash = getOrderHashHex(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, + signedOrder.makerAssetData, + signedOrder.makerAddress, ); const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, + signedOrder.makerAssetData, + signedOrder.makerAddress, ); const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync( zrxTokenAddress, - signedOrder.maker, + signedOrder.makerAddress, ); const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync( zrxTokenAddress, - signedOrder.maker, + signedOrder.makerAddress, ); const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash); const cancelledTakerTokenAmount = await this._orderFilledCancelledFetcher.getCancelledTakerAmountAsync( @@ -106,8 +107,8 @@ export class OrderStateUtils { const unavailableTakerTokenAmount = await this._orderFilledCancelledFetcher.getUnavailableTakerAmountAsync( orderHash, ); - const totalMakerTokenAmount = signedOrder.makerTokenAmount; - const totalTakerTokenAmount = signedOrder.takerTokenAmount; + const totalMakerTokenAmount = signedOrder.makerAssetAmount; + const totalTakerTokenAmount = signedOrder.takerAssetAmount; const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount); const remainingMakerTokenAmount = remainingTakerTokenAmount .times(totalMakerTokenAmount) @@ -115,7 +116,8 @@ export class OrderStateUtils { const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]); const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]); - const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress; + const zrxAssetData = assetProxyUtils.encodeERC20ProxyData(zrxTokenAddress); + const isMakerTokenZRX = signedOrder.makerAssetData === zrxAssetData; const remainingFillableCalculator = new RemainingFillableCalculator( signedOrder, isMakerTokenZRX, diff --git a/packages/order-utils/src/remaining_fillable_calculator.ts b/packages/order-utils/src/remaining_fillable_calculator.ts index 184c13aa4..b291d8ea9 100644 --- a/packages/order-utils/src/remaining_fillable_calculator.ts +++ b/packages/order-utils/src/remaining_fillable_calculator.ts @@ -23,7 +23,7 @@ export class RemainingFillableCalculator { this._remainingMakerTokenAmount = remainingMakerTokenAmount; this._remainingMakerFeeAmount = remainingMakerTokenAmount .times(signedOrder.makerFee) - .dividedToIntegerBy(signedOrder.makerTokenAmount); + .dividedToIntegerBy(signedOrder.makerAssetAmount); } public computeRemainingMakerFillable(): BigNumber { if (this._hasSufficientFundsForFeeAndTransferAmount()) { @@ -36,8 +36,8 @@ export class RemainingFillableCalculator { } public computeRemainingTakerFillable(): BigNumber { return this.computeRemainingMakerFillable() - .times(this._signedOrder.takerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerTokenAmount); + .times(this._signedOrder.takerAssetAmount) + .dividedToIntegerBy(this._signedOrder.makerAssetAmount); } private _hasSufficientFundsForFeeAndTransferAmount(): boolean { if (this._isMakerTokenZRX) { @@ -59,7 +59,7 @@ export class RemainingFillableCalculator { } private _calculatePartiallyFillableMakerTokenAmount(): BigNumber { // Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1 - const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee); + const orderToFeeRatio = this._signedOrder.makerAssetAmount.dividedBy(this._signedOrder.makerFee); // The number of times the maker can fill the order, if each fill only required the transfer of a single // baseUnit of fee tokens. // Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2 @@ -81,10 +81,10 @@ export class RemainingFillableCalculator { // When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored. // This can result in a RoundingError being thrown by the Exchange Contract. const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits - .times(this._signedOrder.makerTokenAmount) + .times(this._signedOrder.makerAssetAmount) .dividedToIntegerBy(this._signedOrder.makerFee); const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits - .times(this._signedOrder.makerTokenAmount) + .times(this._signedOrder.makerAssetAmount) .dividedToIntegerBy(this._signedOrder.makerFee); const partiallyFillableAmount = BigNumber.min( partiallyFillableMakerTokenAmount, diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index ebd636b20..106bbf4e8 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -1,28 +1,97 @@ import { schemas } from '@0xproject/json-schemas'; -import { ECSignature, Provider } from '@0xproject/types'; +import { ECSignature, Provider, SignatureType } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { artifacts } from './artifacts'; import { assert } from './assert'; +import { ExchangeContract } from './generated_contract_wrappers/exchange'; +import { ISignerContract } from './generated_contract_wrappers/i_signer'; import { OrderError } from './types'; /** - * Verifies that the elliptic curve signature `signature` was generated - * by signing `data` with the private key corresponding to the `signerAddress` address. + * Verifies that the provided signature is valid according to the 0x Protocol smart contracts * @param data The hex encoded data signed by the supplied signature. * @param signature An object containing the elliptic curve signature parameters. * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. * @return Whether the signature is valid for the supplied signerAddress and data. */ -export function isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { +export async function isValidSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, +): Promise<boolean> { + const signatureTypeIndexIfExists = getSignatureTypeIndexIfExists(signature); + if (_.isUndefined(signatureTypeIndexIfExists)) { + throw new Error(`Unrecognized signatureType in signature: ${signature}`); + } + + switch (signatureTypeIndexIfExists) { + case SignatureType.Illegal: + case SignatureType.Invalid: + return false; + + // Question: Does it make sense to handle this? + case SignatureType.Caller: + return true; + + // TODO: Rename this type to `EthSign` b/c multiple of the signature + // types use ECRecover... + case SignatureType.Ecrecover: { + const ecSignature = parseECSignature(signature); + const dataBuff = ethUtil.toBuffer(data); + const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + const msgHash = ethUtil.bufferToHex(msgHashBuff); + return isValidECSignature(msgHash, ecSignature, signerAddress); + } + + case SignatureType.EIP712: { + const ecSignature = parseECSignature(signature); + return isValidECSignature(data, ecSignature, signerAddress); + } + + case SignatureType.Trezor: { + const dataBuff = ethUtil.toBuffer(data); + const msgHashBuff = hashTrezorPersonalMessage(dataBuff); + const msgHash = ethUtil.bufferToHex(msgHashBuff); + const ecSignature = parseECSignature(signature); + return isValidECSignature(msgHash, ecSignature, signerAddress); + } + + // TODO: Rename Contract -> Wallet + case SignatureType.Contract: { + const signerContract = new ISignerContract(artifacts.ISigner.abi, signerAddress, provider); + const isValid = await signerContract.isValidSignature.callAsync(data, signature); + return isValid; + } + + case SignatureType.PreSigned: { + const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider); + const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress); + return true; + } + + default: + throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); + } +} + +export function getVRSHexString(ecSignature: ECSignature): string { + const vrs = `0x${intToHex(ecSignature.v)}${ethUtil.stripHexPrefix(ecSignature.r)}${ethUtil.stripHexPrefix( + ecSignature.s, + )}`; + return vrs; +} + +export function isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean { assert.isHexString('data', data); assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); assert.isETHAddressHex('signerAddress', signerAddress); const normalizedSignerAddress = signerAddress.toLowerCase(); - const dataBuff = ethUtil.toBuffer(data); - const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + const msgHashBuff = ethUtil.toBuffer(data); try { const pubKey = ethUtil.ecrecover( msgHashBuff, @@ -36,6 +105,7 @@ export function isValidSignature(data: string, signature: ECSignature, signerAdd return false; } } + /** * Signs an orderHash and returns it's elliptic curve signature. * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 @@ -48,7 +118,7 @@ export function isValidSignature(data: string, signature: ECSignature, signerAdd * before sending the request. * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. */ -export async function signOrderHashAsync( +export async function ecSignOrderHashAsync( provider: Provider, orderHash: string, signerAddress: string, @@ -76,7 +146,7 @@ export async function signOrderHashAsync( const validVParamValues = [27, 28]; const ecSignatureVRS = parseSignatureHexAsVRS(signature); if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress); + const isValidVRSSignature = isValidECSignature(orderHash, ecSignatureVRS, normalizedSignerAddress); if (isValidVRSSignature) { return ecSignatureVRS; } @@ -84,7 +154,7 @@ export async function signOrderHashAsync( const ecSignatureRSV = parseSignatureHexAsRSV(signature); if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress); + const isValidRSVSignature = isValidECSignature(orderHash, ecSignatureRSV, normalizedSignerAddress); if (isValidRSVSignature) { return ecSignatureRSV; } @@ -93,6 +163,39 @@ export async function signOrderHashAsync( throw new Error(OrderError.InvalidSignature); } +function hashTrezorPersonalMessage(message: Buffer): Buffer { + const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.length)); + return ethUtil.sha3(Buffer.concat([prefix, message])); +} + +function parseECSignature(signature: string): ECSignature { + const signatureTypeIndexIfExists = getSignatureTypeIndexIfExists(signature); + const ecSignatureTypes = [SignatureType.Ecrecover, SignatureType.EIP712, SignatureType.Trezor]; + const isECSignatureType = _.includes(ecSignatureTypes, signatureTypeIndexIfExists); + if (!isECSignatureType) { + throw new Error(`Cannot parse non-ECSignature type: ${signatureTypeIndexIfExists}`); + } + + // tslint:disable-next-line:custom-no-magic-numbers + const vrsHex = `0x${signature.substr(4)}`; + const ecSignature = parseSignatureHexAsVRS(vrsHex); + + return ecSignature; +} + +function intToHex(i: number): string { + const hex = ethUtil.bufferToHex(ethUtil.toBuffer(i)); + return hex; +} + +function getSignatureTypeIndexIfExists(signature: string): number { + const unprefixedSignature = ethUtil.stripHexPrefix(signature); + const signatureTypeHex = unprefixedSignature.substr(0, 2); + const base = 16; + const signatureTypeInt = parseInt(signatureTypeHex, base); + return signatureTypeInt; +} + function parseSignatureHexAsVRS(signatureHex: string): ECSignature { const signatureBuffer = ethUtil.toBuffer(signatureHex); let v = signatureBuffer[0]; diff --git a/packages/order-utils/test/order_hash_test.ts b/packages/order-utils/test/order_hash_test.ts index db5489509..7cb87c8ab 100644 --- a/packages/order-utils/test/order_hash_test.ts +++ b/packages/order-utils/test/order_hash_test.ts @@ -1,10 +1,11 @@ import { web3Factory } from '@0xproject/dev-utils'; +import { Order } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import 'make-promises-safe'; import 'mocha'; -import { constants, getOrderHashHex } from '../src'; +import { constants, orderHashUtils } from '../src'; import { chaiSetup } from './utils/chai_setup'; import { web3Wrapper } from './utils/web3_wrapper'; @@ -14,24 +15,25 @@ const expect = chai.expect; describe('Order hashing', () => { describe('#getOrderHashHex', () => { - const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83'; + const expectedOrderHash = '0x367ad7730eb8b5feab8a9c9f47c6fcba77a2d4df125ee6a59cc26ac955710f7e'; const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b'; - const order = { - maker: constants.NULL_ADDRESS, - taker: constants.NULL_ADDRESS, - feeRecipient: constants.NULL_ADDRESS, - makerTokenAddress: constants.NULL_ADDRESS, - takerTokenAddress: constants.NULL_ADDRESS, - exchangeContractAddress: fakeExchangeContractAddress, + const order: Order = { + makerAddress: constants.NULL_ADDRESS, + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + makerAssetData: constants.NULL_ADDRESS, + takerAssetData: constants.NULL_ADDRESS, + exchangeAddress: fakeExchangeContractAddress, salt: new BigNumber(0), makerFee: new BigNumber(0), takerFee: new BigNumber(0), - makerTokenAmount: new BigNumber(0), - takerTokenAmount: new BigNumber(0), - expirationUnixTimestampSec: new BigNumber(0), + makerAssetAmount: new BigNumber(0), + takerAssetAmount: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), }; it('calculates the order hash', async () => { - const orderHash = getOrderHashHex(order); + const orderHash = orderHashUtils.getOrderHashHex(order); expect(orderHash).to.be.equal(expectedOrderHash); }); it('throws a readable error message if taker format is invalid', async () => { @@ -41,7 +43,7 @@ describe('Order hashing', () => { }; const expectedErrorMessage = 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; - expect(() => getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage); + expect(() => orderHashUtils.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage); }); }); }); diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index e24fa0ce5..4babd5582 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -7,8 +7,8 @@ import 'make-promises-safe'; import 'mocha'; import * as Sinon from 'sinon'; -import { generatePseudoRandomSalt, isValidOrderHash, isValidSignature, signOrderHashAsync } from '../src'; -import * as signatureUtils from '../src/signature_utils'; +import { ecSignOrderHashAsync, generatePseudoRandomSalt, orderHashUtils } from '../src'; +import { isValidECSignature, isValidSignatureAsync } from '../src/signature_utils'; import { chaiSetup } from './utils/chai_setup'; import { provider, web3Wrapper } from './utils/web3_wrapper'; @@ -20,7 +20,62 @@ const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; describe('Signature utils', () => { describe('#isValidSignature', () => { - // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes + let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; + const ethSignSignature = + '0x031B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254'; + let address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; + + it("should return false if the data doesn't pertain to the signature & address", async () => { + expect(await isValidSignatureAsync(provider, '0x0', ethSignSignature, address)).to.be.false(); + }); + it("should return false if the address doesn't pertain to the signature & data", async () => { + const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; + expect( + await isValidSignatureAsync(provider, dataHex, ethSignSignature, validUnrelatedAddress), + ).to.be.false(); + }); + it("should return false if the signature doesn't pertain to the dataHex & address", async () => { + const signatureArray = ethSignSignature.split(''); + // tslint:disable-next-line:custom-no-magic-numbers + signatureArray[5] = 'C'; // V = 28, instead of 27 + const wrongSignature = signatureArray.join(''); + expect(await isValidSignatureAsync(provider, dataHex, wrongSignature, address)).to.be.false(); + }); + + it('should throw if signatureType is invalid', () => { + const signatureArray = ethSignSignature.split(''); + signatureArray[3] = '9'; // SignatureType w/ index 9 doesn't exist + const signatureWithInvalidType = signatureArray.join(''); + expect(isValidSignatureAsync(provider, dataHex, signatureWithInvalidType, address)).to.be.rejected(); + }); + + it('should return true for a valid Ecrecover (EthSign) signature', async () => { + const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, ethSignSignature, address); + expect(isValidSignatureLocal).to.be.true(); + }); + + it('should return true for a valid EIP712 signature', async () => { + dataHex = '0xa1d7403bcbbcd75ec233cfd6584ff8dabed677d0e9bb32c2bea94e9dd8a109da'; + address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; + const eip712Signature = + '0x041bdde07aac4bf12c12ddbb155919c43eba4146a2cfcf904a862950dbebe332554c6674975603eb5a4eaf8fd7f2e06350267e5b36cda9851a89f8bb49fe2fc9afe2'; + const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, eip712Signature, address); + expect(isValidSignatureLocal).to.be.true(); + }); + + it('should return true for a valid Trezor signature', async () => { + dataHex = '0xd0d994e31c88f33fd8a572552a70ed339de579e5ba49ee1d17cc978bbe1cdd21'; + address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; + const trezorSignature = + '0x051ce4760660e6495b5ae6723087bea073b3a99ce98ea81fdf00c240279c010e63d05b87bc34c4d67d4776e8d5aeb023a67484f4eaf0fd353b40893e5101e845cd99'; + const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, trezorSignature, address); + expect(isValidSignatureLocal).to.be.true(); + }); + + // TODO: remaining sigs + }); + describe('#isValidECSignature', () => { + // The Exchange smart contract `isValidECSignature` 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 = { @@ -30,18 +85,18 @@ describe('Signature utils', () => { }; const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(isValidSignature('0x0', signature, address)).to.be.false(); + expect(isValidECSignature('0x0', signature, address)).to.be.false(); }); it("should return false if the address doesn't pertain to the signature & data", async () => { const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; - expect(isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false(); + expect(isValidECSignature(dataHex, signature, validUnrelatedAddress)).to.be.false(); }); it("should return false if the signature doesn't pertain to the dataHex & address", async () => { const wrongSignature = _.assign({}, signature, { v: 28 }); - expect(isValidSignature(dataHex, wrongSignature, address)).to.be.false(); + expect(isValidECSignature(dataHex, wrongSignature, address)).to.be.false(); }); it('should return true if the signature does pertain to the dataHex & address', async () => { - const isValidSignatureLocal = isValidSignature(dataHex, signature, address); + const isValidSignatureLocal = isValidECSignature(dataHex, signature, address); expect(isValidSignatureLocal).to.be.true(); }); }); @@ -60,20 +115,20 @@ describe('Signature utils', () => { }); describe('#isValidOrderHash', () => { it('returns false if the value is not a hex string', () => { - const isValid = isValidOrderHash('not a hex'); + const isValid = orderHashUtils.isValidOrderHash('not a hex'); expect(isValid).to.be.false(); }); it('returns false if the length is wrong', () => { - const isValid = isValidOrderHash('0xdeadbeef'); + const isValid = orderHashUtils.isValidOrderHash('0xdeadbeef'); expect(isValid).to.be.false(); }); it('returns true if order hash is correct', () => { const orderHashLength = 65; - const isValid = isValidOrderHash('0x' + Array(orderHashLength).join('0')); + const isValid = orderHashUtils.isValidOrderHash('0x' + Array(orderHashLength).join('0')); expect(isValid).to.be.true(); }); }); - describe('#signOrderHashAsync', () => { + describe('#ecSignOrderHashAsync', () => { let stubs: Sinon.SinonStub[] = []; let makerAddress: string; before(async () => { @@ -92,7 +147,7 @@ describe('Signature utils', () => { r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', }; - const ecSignature = await signOrderHashAsync( + const ecSignature = await ecSignOrderHashAsync( provider, orderHash, makerAddress, @@ -126,7 +181,7 @@ describe('Signature utils', () => { }, }; - const ecSignature = await signOrderHashAsync( + const ecSignature = await ecSignOrderHashAsync( fakeProvider, orderHash, makerAddress, @@ -157,7 +212,7 @@ describe('Signature utils', () => { }, }; - const ecSignature = await signOrderHashAsync( + const ecSignature = await ecSignOrderHashAsync( fakeProvider, orderHash, makerAddress, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 7831f580e..8ec82034d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -292,18 +292,15 @@ export interface Order { makerAssetData: string; takerAssetData: string; salt: BigNumber; + exchangeAddress: string; feeRecipientAddress: string; expirationTimeSeconds: BigNumber; } -export interface SignedOrder extends UnsignedOrder { +export interface SignedOrder extends Order { signature: string; } -export interface UnsignedOrder extends Order { - exchangeAddress: string; -} - /** * Elliptic Curve signature */ @@ -386,3 +383,46 @@ export interface Token { symbol: string; decimals: number; } + +export enum SignatureType { + Illegal, + Invalid, + Caller, + Ecrecover, + EIP712, + Trezor, + Contract, + PreSigned, +} + +/** + * Elliptic Curve signature + */ +export interface ECSignature { + v: number; + r: string; + s: string; +} + +export enum AssetProxyId { + INVALID, + ERC20, + ERC721, +} + +export interface ERC20ProxyData { + assetProxyId: AssetProxyId; + tokenAddress: string; +} + +export interface ERC721ProxyData { + assetProxyId: AssetProxyId; + tokenAddress: string; + tokenId: BigNumber; +} + +export interface ProxyData { + assetProxyId: AssetProxyId; + tokenAddress?: string; + data?: any; +} |