diff --git a/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol b/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol index 5de8010532..7d32e7d758 100644 --- a/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol +++ b/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol @@ -29,16 +29,18 @@ contract LibZeroExTransaction is // keccak256(abi.encodePacked( // "ZeroExTransaction(", // "uint256 salt,", + // "uint256 expirationTimeSeconds," // "address signerAddress,", // "bytes data", // ")" // )); - bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x213c6f636f3ea94e701c0adf9b2624aa45a6c694f9a292c094f9a81c24b5df4c; + bytes32 constant public EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x6b4c70d217b44d0ff0d3bf7aeb18eb8604c5cd06f615a4b497aeefa4f01d2775; struct ZeroExTransaction { - uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash. - address signerAddress; // Address of transaction signer. - bytes data; // AbiV2 encoded calldata. + uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash. + uint256 expirationTimeSeconds; // Timestamp in seconds at which transaction expires. + address signerAddress; // Address of transaction signer. + bytes data; // AbiV2 encoded calldata. } /// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of the Exchange contract. @@ -65,12 +67,14 @@ contract LibZeroExTransaction is bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; bytes memory data = transaction.data; uint256 salt = transaction.salt; + uint256 expirationTimeSeconds = transaction.expirationTimeSeconds; address signerAddress = transaction.signerAddress; // Assembly for more efficiently computing: // keccak256(abi.encodePacked( // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, // transaction.salt, + // transaction.expirationTimeSeconds, // uint256(transaction.signerAddress), // keccak256(transaction.data) // )); @@ -84,11 +88,12 @@ contract LibZeroExTransaction is mstore(memPtr, schemaHash) // hash of schema mstore(add(memPtr, 32), salt) // salt - mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress - mstore(add(memPtr, 96), dataHash) // hash of data + mstore(add(memPtr, 64), expirationTimeSeconds) // expirationTimeSeconds + mstore(add(memPtr, 96), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress + mstore(add(memPtr, 128), dataHash) // hash of data // Compute hash - result := keccak256(memPtr, 128) + result := keccak256(memPtr, 160) } return result; } diff --git a/contracts/exchange-libs/contracts/test/TestLibs.sol b/contracts/exchange-libs/contracts/test/TestLibs.sol index 8548e0fbdd..b27be8af8d 100644 --- a/contracts/exchange-libs/contracts/test/TestLibs.sol +++ b/contracts/exchange-libs/contracts/test/TestLibs.sol @@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2; import "../src/LibEIP712ExchangeDomain.sol"; import "../src/LibMath.sol"; import "../src/LibOrder.sol"; +import "../src/LibZeroExTransaction.sol"; import "../src/LibFillResults.sol"; @@ -30,6 +31,7 @@ contract TestLibs is LibEIP712ExchangeDomain, LibMath, LibOrder, + LibZeroExTransaction, LibFillResults { constructor (uint256 chainId) diff --git a/contracts/exchange-libs/test/exchange_libs.ts b/contracts/exchange-libs/test/exchange_libs.ts index 4d9b125115..57f3421918 100644 --- a/contracts/exchange-libs/test/exchange_libs.ts +++ b/contracts/exchange-libs/test/exchange_libs.ts @@ -8,14 +8,14 @@ import { web3Wrapper, } from '@0x/contracts-test-utils'; import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { assetDataUtils, orderHashUtils, transactionHashUtils } from '@0x/order-utils'; import { constants as orderConstants } from '@0x/order-utils/lib/src/constants'; import { SignedOrder } from '@0x/types'; import { BigNumber, providerUtils } from '@0x/utils'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; -import { TestLibsContract } from '../generated-wrappers/test_libs'; +import { TestLibsContract } from '../src'; import { artifacts } from '../src/artifacts'; import { stringifySchema } from './utils'; @@ -56,7 +56,6 @@ describe('Exchange libs', () => { txDefaults, new BigNumber(alternateChainId), ); - const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, makerAddress, @@ -154,6 +153,33 @@ describe('Exchange libs', () => { }); }); + describe('LibZeroExTransaction', () => { + describe('EIP712ZeroExTransactionSchemaHash', () => { + it('should return the correct schema hash', async () => { + const schemaHash = await libs.EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH.callAsync(); + const schemaString = + 'ZeroExTransaction(uint256 salt,uint256 expirationTimeSeconds,address signerAddress,bytes data)'; + const expectedSchemaHash = ethUtil.addHexPrefix(ethUtil.bufferToHex(ethUtil.sha3(schemaString))); + expect(schemaHash).to.equal(expectedSchemaHash); + }); + it('should return the correct transactionHash', async () => { + const transaction = { + salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + signerAddress: constants.NULL_ADDRESS, + data: constants.NULL_BYTES, + domain: { + verifyingContractAddress: libs.address, + chainId, + }, + }; + const transactionHash = await libs.getTransactionHash.callAsync(transaction); + const expectedTransactionHash = transactionHashUtils.getTransactionHashHex(transaction); + expect(transactionHash).to.equal(expectedTransactionHash); + }); + }); + }); + describe('LibEIP712', () => { it('should return the correct domain separator schema hash', async () => { const schema = stringifySchema(orderConstants.DEFAULT_DOMAIN_SCHEMA); diff --git a/packages/json-schemas/schemas/zero_ex_transaction_schema.json b/packages/json-schemas/schemas/zero_ex_transaction_schema.json index b0578b9968..240e02ab7e 100644 --- a/packages/json-schemas/schemas/zero_ex_transaction_schema.json +++ b/packages/json-schemas/schemas/zero_ex_transaction_schema.json @@ -4,8 +4,9 @@ "data": { "$ref": "/hexSchema" }, "signerAddress": { "$ref": "/addressSchema" }, "salt": { "$ref": "/wholeNumberSchema" }, + "expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }, "domain": { "$ref": "/eip712DomainSchema" } }, - "required": ["data", "salt", "signerAddress", "domain"], + "required": ["data", "salt", "expirationTimeSeconds", "signerAddress", "domain"], "type": "object" } diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index 8d7272c146..8cbf2b1134 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -133,6 +133,7 @@ export const constants = { name: 'ZeroExTransaction', parameters: [ { name: 'salt', type: 'uint256' }, + { name: 'expirationTimeSeconds', type: 'uint256' }, { name: 'signerAddress', type: 'address' }, { name: 'data', type: 'bytes' }, ], diff --git a/packages/order-utils/test/eip712_utils_test.ts b/packages/order-utils/test/eip712_utils_test.ts index f92a42fc1d..ec306c8973 100644 --- a/packages/order-utils/test/eip712_utils_test.ts +++ b/packages/order-utils/test/eip712_utils_test.ts @@ -57,7 +57,8 @@ describe('EIP712 Utils', () => { describe('createZeroExTransactionTypedData', () => { it('adds in the EIP712DomainSeparator', () => { const typedData = eip712Utils.createZeroExTransactionTypedData({ - salt: new BigNumber('0'), + salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), data: constants.NULL_BYTES, signerAddress: constants.NULL_ADDRESS, domain: { diff --git a/packages/order-utils/test/transaction_hash_test.ts b/packages/order-utils/test/transaction_hash_test.ts index 77d838081c..d38961c01f 100644 --- a/packages/order-utils/test/transaction_hash_test.ts +++ b/packages/order-utils/test/transaction_hash_test.ts @@ -14,12 +14,13 @@ const expect = chai.expect; describe('0x transaction hashing', () => { describe('#getTransactionHashHex', () => { - const expectedTransactionHash = '0x834125acbd69d6e2e706df216865728e9b63bfd7c8bcecb5987d9d02ea62ecd5'; + const expectedTransactionHash = '0x9779e4ca195f8c9c6f137f495599e9a1944806310b64748479bfa6c6b1ae7eb4'; const fakeVerifyingContractAddress = '0x5e72914535f202659083db3a02c984188fa26e9f'; const fakeChainId = 1337; const transaction: ZeroExTransaction = { signerAddress: constants.NULL_ADDRESS, salt: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), data: constants.NULL_BYTES, domain: { verifyingContractAddress: fakeVerifyingContractAddress, @@ -38,6 +39,7 @@ describe('0x transaction hashing', () => { const transactionHash = transactionHashUtils.getTransactionHashHex({ ...transaction, salt: '0', + expirationTimeSeconds: '0', } as any); expect(transactionHash).to.be.equal(expectedTransactionHash); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 7b0e6500d2..dc97eebbac 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -55,6 +55,7 @@ export enum MarketOperation { */ export interface ZeroExTransaction { salt: BigNumber; + expirationTimeSeconds: BigNumber; signerAddress: string; data: string; domain: EIP712DomainWithDefaultSchema;