From f9292a8fb8110d4fee0effe2d7263a83c1a62f04 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Mon, 29 Jul 2019 18:26:55 -0700 Subject: [PATCH] Added unit tests for LibEIP712 --- contracts/test-utils/src/constants.ts | 1 + contracts/utils/compiler.json | 1 + contracts/utils/contracts/src/LibEIP712.sol | 2 +- .../utils/contracts/test/TestLibEIP712.sol | 52 +++++++++ contracts/utils/package.json | 2 +- contracts/utils/src/artifacts.ts | 2 + contracts/utils/src/wrappers.ts | 1 + contracts/utils/test/lib_eip712.ts | 107 ++++++++++++++++++ contracts/utils/tsconfig.json | 1 + packages/utils/src/sign_typed_data_utils.ts | 25 +++- 10 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 contracts/utils/contracts/test/TestLibEIP712.sol create mode 100644 contracts/utils/test/lib_eip712.ts diff --git a/contracts/test-utils/src/constants.ts b/contracts/test-utils/src/constants.ts index 83ea993ed8..3ec8ff987d 100644 --- a/contracts/test-utils/src/constants.ts +++ b/contracts/test-utils/src/constants.ts @@ -42,6 +42,7 @@ export const constants = { NUM_ERC1155_FUNGIBLE_TOKENS_MINT: 4, NUM_ERC1155_NONFUNGIBLE_TOKENS_MINT: 4, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + NULL_BYTES32: '0x0000000000000000000000000000000000000000000000000000000000000000', UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), TESTRPC_PRIVATE_KEYS: _.map(TESTRPC_PRIVATE_KEYS_STRINGS, privateKeyString => ethUtil.toBuffer(privateKeyString)), INITIAL_ERC20_BALANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18), diff --git a/contracts/utils/compiler.json b/contracts/utils/compiler.json index 289cf93c27..a31630ca81 100644 --- a/contracts/utils/compiler.json +++ b/contracts/utils/compiler.json @@ -36,6 +36,7 @@ "test/TestLibAddress.sol", "test/TestLibAddressArray.sol", "test/TestLibBytes.sol", + "test/TestLibEIP712.sol", "test/TestOwnable.sol", "test/TestReentrancyGuard.sol", "test/TestSafeMath.sol" diff --git a/contracts/utils/contracts/src/LibEIP712.sol b/contracts/utils/contracts/src/LibEIP712.sol index 62a1f38dfb..32ca6ff117 100644 --- a/contracts/utils/contracts/src/LibEIP712.sol +++ b/contracts/utils/contracts/src/LibEIP712.sol @@ -1,6 +1,6 @@ /* - Copyright 2018 ZeroEx Intl. + Copyright 2019 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/contracts/utils/contracts/test/TestLibEIP712.sol b/contracts/utils/contracts/test/TestLibEIP712.sol new file mode 100644 index 0000000000..807651e5b3 --- /dev/null +++ b/contracts/utils/contracts/test/TestLibEIP712.sol @@ -0,0 +1,52 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; + +import "../src/LibEIP712.sol"; + + +contract TestLibEIP712 is + LibEIP712 +{ + function externalHashEIP712DomainSeperator( + string calldata name, + string calldata version, + uint256 chainid, + address verifyingcontractaddress + ) + external + pure + returns (bytes32) + { + return _hashEIP712Domain( + name, + version, + chainid, + verifyingcontractaddress + ); + } + + function externalHashEIP712Message(bytes32 eip712DomainHash, bytes32 hashStruct) + external + pure + returns (bytes32) + { + return _hashEIP712Message(eip712DomainHash, hashStruct); + } +} diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 3ce247b47f..f05094d2fa 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(IOwnable|LibAddress|LibBytes|LibEIP1271|LibEIP712|Ownable|ReentrancyGuard|SafeMath|TestConstants|TestLibAddress|TestLibAddressArray|TestLibBytes|TestOwnable|TestReentrancyGuard|TestSafeMath).json", + "abis": "./generated-artifacts/@(IOwnable|LibAddress|LibBytes|LibEIP1271|LibEIP712|Ownable|ReentrancyGuard|SafeMath|TestConstants|TestLibAddress|TestLibAddressArray|TestLibBytes|TestLibEIP712|TestOwnable|TestReentrancyGuard|TestSafeMath).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/utils/src/artifacts.ts b/contracts/utils/src/artifacts.ts index f04bad4ed6..b72eac333e 100644 --- a/contracts/utils/src/artifacts.ts +++ b/contracts/utils/src/artifacts.ts @@ -17,6 +17,7 @@ import * as TestConstants from '../generated-artifacts/TestConstants.json'; import * as TestLibAddress from '../generated-artifacts/TestLibAddress.json'; import * as TestLibAddressArray from '../generated-artifacts/TestLibAddressArray.json'; import * as TestLibBytes from '../generated-artifacts/TestLibBytes.json'; +import * as TestLibEIP712 from '../generated-artifacts/TestLibEIP712.json'; import * as TestOwnable from '../generated-artifacts/TestOwnable.json'; import * as TestReentrancyGuard from '../generated-artifacts/TestReentrancyGuard.json'; import * as TestSafeMath from '../generated-artifacts/TestSafeMath.json'; @@ -33,6 +34,7 @@ export const artifacts = { TestLibAddress: TestLibAddress as ContractArtifact, TestLibAddressArray: TestLibAddressArray as ContractArtifact, TestLibBytes: TestLibBytes as ContractArtifact, + TestLibEIP712: TestLibEIP712 as ContractArtifact, TestOwnable: TestOwnable as ContractArtifact, TestReentrancyGuard: TestReentrancyGuard as ContractArtifact, TestSafeMath: TestSafeMath as ContractArtifact, diff --git a/contracts/utils/src/wrappers.ts b/contracts/utils/src/wrappers.ts index e2d4ef54cc..5337a00e36 100644 --- a/contracts/utils/src/wrappers.ts +++ b/contracts/utils/src/wrappers.ts @@ -15,6 +15,7 @@ export * from '../generated-wrappers/test_constants'; export * from '../generated-wrappers/test_lib_address'; export * from '../generated-wrappers/test_lib_address_array'; export * from '../generated-wrappers/test_lib_bytes'; +export * from '../generated-wrappers/test_lib_e_i_p712'; export * from '../generated-wrappers/test_ownable'; export * from '../generated-wrappers/test_reentrancy_guard'; export * from '../generated-wrappers/test_safe_math'; diff --git a/contracts/utils/test/lib_eip712.ts b/contracts/utils/test/lib_eip712.ts new file mode 100644 index 0000000000..8aac0495b7 --- /dev/null +++ b/contracts/utils/test/lib_eip712.ts @@ -0,0 +1,107 @@ +import { chaiSetup, constants, hexConcat, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { BigNumber, signTypedDataUtils } from '@0x/utils'; +import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { artifacts, TestLibEIP712Contract } from '../src'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +/** + * Tests a specific instance of EIP712 domain hashing. + * @param lib The LibEIP712 contract to call. + * @param name The name of the domain. + * @param version The version of the domain. + * @param chainId The chain id of the domain. + * @param verifyingContractAddress The verifying contract address of the domain. + */ +async function testHashEIP712DomainAsync( + lib: TestLibEIP712Contract, + name: string, + version: string, + chainId: number, + verifyingContractAddress: string, +): Promise { + const expectedHash = signTypedDataUtils.generateDomainHash({ + name, + version, + chainId, + verifyingContractAddress, + }); + const actualHash = await lib.externalHashEIP712DomainSeperator.callAsync( + name, + version, + new BigNumber(chainId), + verifyingContractAddress, + ); + expect(actualHash).to.be.eq(hexConcat(expectedHash)); +} + +/** + * Tests a specific instance of EIP712 message hashing. + * @param lib The LibEIP712 contract to call. + * @param domainHash The hash of the EIP712 domain of this instance. + * @param hashStruct The hash of the struct of this instance. + */ +async function testHashEIP712MessageAsync( + lib: TestLibEIP712Contract, + domainHash: string, + hashStruct: string, +): Promise { + const input = '0x1901'.concat(domainHash.slice(2, domainHash.length).concat(hashStruct.slice(2, hashStruct.length))); + const expectedHash = '0x'.concat(ethUtil.sha3(input).toString('hex')); + const actualHash = await lib.externalHashEIP712Message.callAsync( + domainHash, + hashStruct, + ); + expect(actualHash).to.be.eq(expectedHash); +} + +describe('LibEIP712', () => { + let lib: TestLibEIP712Contract; + + before(async () => { + await blockchainLifecycle.startAsync(); + // Deploy SafeMath + lib = await TestLibEIP712Contract.deployFrom0xArtifactAsync( + artifacts.TestLibEIP712, + provider, + txDefaults, + ); + }); + + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('_hashEIP712Domain', async () => { + it('should correctly hash empty input', async () => { + await testHashEIP712DomainAsync( + lib, + '', + '', + 0, + constants.NULL_ADDRESS, + ); + }); + }); + + describe('_hashEIP712Message', () => { + it('should correctly hash empty input', async () => { + /* + const expectedHash = hashEIP712Message(constants.NULL_BYTES32, constants.NULL_BYTES32); + const actualHash = await lib.externalHashEIP712Message.callAsync(constants.NULL_BYTES32, constants.NULL_BYTES32); + expect(actualHash).to.be.eq(expectedHash); + */ + await testHashEIP712MessageAsync( + lib, + constants.NULL_BYTES32, + constants.NULL_BYTES32, + ); + }); + }); +}); diff --git a/contracts/utils/tsconfig.json b/contracts/utils/tsconfig.json index 7c02167004..d63200ed62 100644 --- a/contracts/utils/tsconfig.json +++ b/contracts/utils/tsconfig.json @@ -15,6 +15,7 @@ "generated-artifacts/TestLibAddress.json", "generated-artifacts/TestLibAddressArray.json", "generated-artifacts/TestLibBytes.json", + "generated-artifacts/TestLibEIP712.json", "generated-artifacts/TestOwnable.json", "generated-artifacts/TestReentrancyGuard.json", "generated-artifacts/TestSafeMath.json" diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts index 001509c899..824d8f8cc8 100644 --- a/packages/utils/src/sign_typed_data_utils.ts +++ b/packages/utils/src/sign_typed_data_utils.ts @@ -1,10 +1,11 @@ -import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0x/types'; +import { EIP712DomainWithDefaultSchema, EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0x/types'; import * as ethUtil from 'ethereumjs-util'; import * as ethers from 'ethers'; import * as _ from 'lodash'; import { BigNumber } from './configured_bignumber'; + export const signTypedDataUtils = { /** * Generates the EIP712 Typed Data hash for signing @@ -20,6 +21,28 @@ export const signTypedDataUtils = { ]), ); }, + /** + * Generates the hash of a EIP712 Domain with the default schema + * @param domain An EIP712 domain with the default schema containing a name, version, chain id, + * and verifying address. + * @return A buffer that contains the hash of the domain. + */ + generateDomainHash(domain: EIP712Object): Buffer { + return signTypedDataUtils._structHash( + 'EIP712Domain', + domain, + // HACK(jalextowle): When we consolidate our testing packages into test-utils, we can use a constant + // to eliminate code duplication. At the moment, there isn't a good way to do that because of cyclic-dependencies. + { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContractAddress', type: 'address' }, + ] + } as EIP712Types, + ); + }, _findDependencies(primaryType: string, types: EIP712Types, found: string[] = []): string[] { if (found.includes(primaryType) || types[primaryType] === undefined) { return found;