From f4097804553f8fbaac5a456b25d950f0ed91735b Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Wed, 13 Feb 2019 19:13:30 -0800 Subject: [PATCH] Add ApprovalFactory class --- contracts/tec/compiler.json | 2 +- contracts/tec/contracts/test/TestMixins.sol | 29 ++++++++ contracts/tec/package.json | 2 +- contracts/tec/src/artifacts.ts | 4 +- contracts/tec/src/wrappers.ts | 2 +- contracts/tec/test/libs.ts | 8 +-- contracts/tec/test/mixins.ts | 67 ++++++++++++------- contracts/tec/test/utils/approval_factory.ts | 28 ++++++++ .../utils/{approval_hash.ts => hash_utils.ts} | 14 ++-- contracts/tec/test/utils/index.ts | 5 +- contracts/tec/test/utils/signing_utils.ts | 30 +++++++++ .../tec/test/utils/tec_transaction_factory.ts | 43 ++++++------ contracts/tec/test/utils/types.ts | 4 ++ contracts/tec/tsconfig.json | 4 +- 14 files changed, 179 insertions(+), 63 deletions(-) create mode 100644 contracts/tec/contracts/test/TestMixins.sol rename contracts/tec/test/utils/{approval_hash.ts => hash_utils.ts} (81%) create mode 100644 contracts/tec/test/utils/signing_utils.ts diff --git a/contracts/tec/compiler.json b/contracts/tec/compiler.json index ffa41c6ed7..68d864c189 100644 --- a/contracts/tec/compiler.json +++ b/contracts/tec/compiler.json @@ -16,5 +16,5 @@ } } }, - "contracts": ["src/MixinSignatureValidator.sol", "src/TEC.sol", "test/TestLibs.sol"] + "contracts": ["src/TEC.sol", "test/TestLibs.sol", "test/TestMixins.sol"] } diff --git a/contracts/tec/contracts/test/TestMixins.sol b/contracts/tec/contracts/test/TestMixins.sol new file mode 100644 index 0000000000..bcb989fd84 --- /dev/null +++ b/contracts/tec/contracts/test/TestMixins.sol @@ -0,0 +1,29 @@ +/* + + Copyright 2018 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.3; +pragma experimental "ABIEncoderV2"; + +import "../src/MixinSignatureValidator.sol"; +import "../src/MixinTECApprovalVerifier.sol"; + + +contract TestMixins is + MixinSignatureValidator, + MixinTECApprovalVerifier +{} diff --git a/contracts/tec/package.json b/contracts/tec/package.json index 65aa11483a..1aa5847591 100644 --- a/contracts/tec/package.json +++ b/contracts/tec/package.json @@ -33,7 +33,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(MixinSignatureValidator|TEC|TestLibs).json", + "abis": "./generated-artifacts/@(IExchange|TEC|TestLibs|TestMixins).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/tec/src/artifacts.ts b/contracts/tec/src/artifacts.ts index 43340f831f..6d007d2b15 100644 --- a/contracts/tec/src/artifacts.ts +++ b/contracts/tec/src/artifacts.ts @@ -5,11 +5,11 @@ */ import { ContractArtifact } from 'ethereum-types'; -import * as MixinSignatureValidator from '../generated-artifacts/MixinSignatureValidator.json'; import * as TEC from '../generated-artifacts/TEC.json'; import * as TestLibs from '../generated-artifacts/TestLibs.json'; +import * as TestMixins from '../generated-artifacts/TestMixins.json'; export const artifacts = { + TestMixins: TestMixins as ContractArtifact, TEC: TEC as ContractArtifact, TestLibs: TestLibs as ContractArtifact, - MixinSignatureValidator: MixinSignatureValidator as ContractArtifact, }; diff --git a/contracts/tec/src/wrappers.ts b/contracts/tec/src/wrappers.ts index 0fff57ed58..69ad17bdfa 100644 --- a/contracts/tec/src/wrappers.ts +++ b/contracts/tec/src/wrappers.ts @@ -3,6 +3,6 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ -export * from '../generated-wrappers/mixin_signature_validator'; export * from '../generated-wrappers/tec'; export * from '../generated-wrappers/test_libs'; +export * from '../generated-wrappers/test_mixins'; diff --git a/contracts/tec/test/libs.ts b/contracts/tec/test/libs.ts index 26f1cf708f..3331603b95 100644 --- a/contracts/tec/test/libs.ts +++ b/contracts/tec/test/libs.ts @@ -3,7 +3,7 @@ import { BlockchainLifecycle } from '@0x/dev-utils'; import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; -import { approvalHashUtils, artifacts, TestLibsContract } from '../src'; +import { artifacts, hashUtils, TestLibsContract } from '../src'; chaiSetup.configure(); const expect = chai.expect; @@ -36,7 +36,7 @@ describe('Libs tests', () => { signerAddress: constants.NULL_ADDRESS, data: '0x1234', }; - const expectedTxHash = approvalHashUtils._getTransactionHashHex(tx); + const expectedTxHash = hashUtils.getTransactionHashHex(tx); const txHash = await testLibs.publicGetTransactionHash.callAsync(tx); expect(expectedTxHash).to.eq(txHash); }); @@ -53,11 +53,11 @@ describe('Libs tests', () => { }; const approvalExpirationTimeSeconds = new BigNumber(0); const approval = { - transactionHash: approvalHashUtils._getTransactionHashHex(signedTx), + transactionHash: hashUtils.getTransactionHashHex(signedTx), transactionSignature: signedTx.signature, approvalExpirationTimeSeconds, }; - const expectedApprovalHash = approvalHashUtils.getApprovalHashHex(signedTx, approvalExpirationTimeSeconds); + const expectedApprovalHash = hashUtils.getApprovalHashHex(signedTx, approvalExpirationTimeSeconds); const approvalHash = await testLibs.publicGetTECApprovalHash.callAsync(approval); expect(expectedApprovalHash).to.eq(approvalHash); }); diff --git a/contracts/tec/test/mixins.ts b/contracts/tec/test/mixins.ts index c7e75d6063..96ec2ec9e9 100644 --- a/contracts/tec/test/mixins.ts +++ b/contracts/tec/test/mixins.ts @@ -7,13 +7,19 @@ import { web3Wrapper, } from '@0x/contracts-test-utils'; import { BlockchainLifecycle } from '@0x/dev-utils'; -import { transactionHashUtils } from '@0x/order-utils'; -import { RevertReason } from '@0x/types'; +import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; -import { artifacts, MixinSignatureValidatorContract, TECSignatureType, TECTransactionFactory } from '../src'; +import { + ApprovalFactory, + artifacts, + hashUtils, + TECSignatureType, + TECTransactionFactory, + TestMixinsContract, +} from '../src'; chaiSetup.configure(); const expect = chai.expect; @@ -22,8 +28,10 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('Mixins tests', () => { let transactionSignerAddress: string; let approvalSignerAddress: string; - let signatureValidator: MixinSignatureValidatorContract; + let mixins: TestMixinsContract; let transactionFactory: TECTransactionFactory; + let approvalFactory: ApprovalFactory; + let defaultOrder: SignedOrder; before(async () => { await blockchainLifecycle.startAsync(); @@ -32,16 +40,29 @@ describe('Mixins tests', () => { await blockchainLifecycle.revertAsync(); }); before(async () => { - signatureValidator = await MixinSignatureValidatorContract.deployFrom0xArtifactAsync( - artifacts.MixinSignatureValidator, - provider, - txDefaults, - ); + mixins = await TestMixinsContract.deployFrom0xArtifactAsync(artifacts.TestMixins, provider, txDefaults); const accounts = await web3Wrapper.getAvailableAddressesAsync(); [transactionSignerAddress, approvalSignerAddress] = accounts.slice(0, 2); + defaultOrder = { + exchangeAddress: constants.NULL_ADDRESS, + makerAddress: constants.NULL_ADDRESS, + takerAddress: constants.NULL_ADDRESS, + senderAddress: mixins.address, + feeRecipientAddress: approvalSignerAddress, + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + makerAssetAmount: constants.ZERO_AMOUNT, + takerAssetAmount: constants.ZERO_AMOUNT, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + expirationTimeSeconds: constants.ZERO_AMOUNT, + salt: constants.ZERO_AMOUNT, + signature: constants.NULL_BYTES, + }; const transactionSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[0]; const approvalSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[1]; - transactionFactory = new TECTransactionFactory(transactionSignerPrivateKey, signatureValidator.address); + transactionFactory = new TECTransactionFactory(transactionSignerPrivateKey, mixins.address); + approvalFactory = new ApprovalFactory(approvalSignerPrivateKey); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -54,21 +75,15 @@ describe('Mixins tests', () => { it('should return the correct address using the EthSign signature type', async () => { const data = constants.NULL_BYTES; const transaction = transactionFactory.newSignedTECTransaction(data, TECSignatureType.EthSign); - const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); - const signerAddress = await signatureValidator.getSignerAddress.callAsync( - transactionHash, - transaction.signature, - ); + const transactionHash = hashUtils.getTransactionHashHex(transaction); + const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature); expect(transaction.signerAddress).to.eq(signerAddress); }); it('should return the correct address using the EIP712 signature type', async () => { const data = constants.NULL_BYTES; const transaction = transactionFactory.newSignedTECTransaction(data, TECSignatureType.EIP712); - const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); - const signerAddress = await signatureValidator.getSignerAddress.callAsync( - transactionHash, - transaction.signature, - ); + const transactionHash = hashUtils.getTransactionHashHex(transaction); + const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature); expect(transaction.signerAddress).to.eq(signerAddress); }); it('should revert with with the Illegal signature type', async () => { @@ -79,9 +94,9 @@ describe('Mixins tests', () => { 0, transaction.signature.length - 2, )}${illegalSignatureByte}`; - const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); + const transactionHash = hashUtils.getTransactionHashHex(transaction); expectContractCallFailedAsync( - signatureValidator.getSignerAddress.callAsync(transactionHash, transaction.signature), + mixins.getSignerAddress.callAsync(transactionHash, transaction.signature), RevertReason.SignatureIllegal, ); }); @@ -93,11 +108,15 @@ describe('Mixins tests', () => { 0, transaction.signature.length - 2, )}${invalidSignatureByte}`; - const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); + const transactionHash = hashUtils.getTransactionHashHex(transaction); expectContractCallFailedAsync( - signatureValidator.getSignerAddress.callAsync(transactionHash, transaction.signature), + mixins.getSignerAddress.callAsync(transactionHash, transaction.signature), RevertReason.SignatureUnsupported, ); }); }); + + describe('assertValidSingleOrderApproval', () => {}); + describe('assertValidBatchOrderApproval', () => {}); + describe('assertValidTECApproval', () => {}); }); diff --git a/contracts/tec/test/utils/approval_factory.ts b/contracts/tec/test/utils/approval_factory.ts index e69de29bb2..0e4c4c487a 100644 --- a/contracts/tec/test/utils/approval_factory.ts +++ b/contracts/tec/test/utils/approval_factory.ts @@ -0,0 +1,28 @@ +import { SignedZeroExTransaction } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as ethUtil from 'ethereumjs-util'; + +import { hashUtils, SignedTECApproval, signingUtils, TECSignatureType } from './index'; + +export class ApprovalFactory { + private readonly _privateKey: Buffer; + constructor(privateKey: Buffer) { + this._privateKey = privateKey; + } + public newSignedApproval( + transaction: SignedZeroExTransaction, + approvalExpirationTimeSeconds: BigNumber, + signatureType: TECSignatureType = TECSignatureType.EthSign, + ): SignedTECApproval { + const approvalHashBuff = hashUtils.getApprovalHashBuffer(transaction, approvalExpirationTimeSeconds); + const signatureBuff = signingUtils.signMessage(approvalHashBuff, this._privateKey, signatureType); + const transactionHash = hashUtils.getTransactionHashHex(transaction); + const signedApproval = { + transactionHash, + approvalExpirationTimeSeconds, + transactionSignature: transaction.signature, + approvalSignature: ethUtil.addHexPrefix(signatureBuff.toString('hex')), + }; + return signedApproval; + } +} diff --git a/contracts/tec/test/utils/approval_hash.ts b/contracts/tec/test/utils/hash_utils.ts similarity index 81% rename from contracts/tec/test/utils/approval_hash.ts rename to contracts/tec/test/utils/hash_utils.ts index c25cfeb1a4..f7ea74eef1 100644 --- a/contracts/tec/test/utils/approval_hash.ts +++ b/contracts/tec/test/utils/hash_utils.ts @@ -4,16 +4,16 @@ import { SignedZeroExTransaction, ZeroExTransaction } from '@0x/types'; import { BigNumber, signTypedDataUtils } from '@0x/utils'; import * as _ from 'lodash'; -import { constants } from './constants'; +import { constants } from './index'; -export const approvalHashUtils = { +export const hashUtils = { getApprovalHashBuffer(transaction: SignedZeroExTransaction, approvalExpirationTimeSeconds: BigNumber): Buffer { const domain = { name: constants.TEC_DOMAIN_NAME, version: constants.TEC_DOMAIN_VERSION, verifyingContractAddress: transaction.verifyingContractAddress, }; - const transactionHash = approvalHashUtils._getTransactionHashHex(transaction); + const transactionHash = hashUtils.getTransactionHashHex(transaction); const approval = { transactionHash, transactionSignature: transaction.signature, @@ -31,12 +31,12 @@ export const approvalHashUtils = { return hashBuffer; }, getApprovalHashHex(transaction: SignedZeroExTransaction, approvalExpirationTimeSeconds: BigNumber): string { - const hashHex = `0x${approvalHashUtils + const hashHex = `0x${hashUtils .getApprovalHashBuffer(transaction, approvalExpirationTimeSeconds) .toString('hex')}`; return hashHex; }, - _getTransactionHashBuffer(transaction: ZeroExTransaction | SignedZeroExTransaction): Buffer { + getTransactionHashBuffer(transaction: ZeroExTransaction | SignedZeroExTransaction): Buffer { const domain = { name: constants.TEC_DOMAIN_NAME, version: constants.TEC_DOMAIN_VERSION, @@ -54,8 +54,8 @@ export const approvalHashUtils = { const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData); return hashBuffer; }, - _getTransactionHashHex(transaction: ZeroExTransaction | SignedZeroExTransaction): string { - const hashHex = `0x${approvalHashUtils._getTransactionHashBuffer(transaction).toString('hex')}`; + getTransactionHashHex(transaction: ZeroExTransaction | SignedZeroExTransaction): string { + const hashHex = `0x${hashUtils.getTransactionHashBuffer(transaction).toString('hex')}`; return hashHex; }, }; diff --git a/contracts/tec/test/utils/index.ts b/contracts/tec/test/utils/index.ts index 595702c89e..a1e6247566 100644 --- a/contracts/tec/test/utils/index.ts +++ b/contracts/tec/test/utils/index.ts @@ -1,3 +1,6 @@ -export { approvalHashUtils } from './approval_hash'; +export { hashUtils } from './hash_utils'; +export { signingUtils } from './signing_utils'; export { TECTransactionFactory } from './tec_transaction_factory'; +export { ApprovalFactory } from './approval_factory'; +export { constants } from './constants'; export * from './types'; diff --git a/contracts/tec/test/utils/signing_utils.ts b/contracts/tec/test/utils/signing_utils.ts new file mode 100644 index 0000000000..12ed71175b --- /dev/null +++ b/contracts/tec/test/utils/signing_utils.ts @@ -0,0 +1,30 @@ +import * as ethUtil from 'ethereumjs-util'; + +import { TECSignatureType } from './types'; + +export const signingUtils = { + signMessage(message: Buffer, privateKey: Buffer, signatureType: TECSignatureType): Buffer { + if (signatureType === TECSignatureType.EthSign) { + const prefixedMessage = ethUtil.hashPersonalMessage(message); + const ecSignature = ethUtil.ecsign(prefixedMessage, privateKey); + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(signatureType), + ]); + return signature; + } else if (signatureType === TECSignatureType.EIP712) { + const ecSignature = ethUtil.ecsign(message, privateKey); + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(signatureType), + ]); + return signature; + } else { + throw new Error(`${signatureType} is not a valid signature type`); + } + }, +}; diff --git a/contracts/tec/test/utils/tec_transaction_factory.ts b/contracts/tec/test/utils/tec_transaction_factory.ts index ea17a53133..8d2191717a 100644 --- a/contracts/tec/test/utils/tec_transaction_factory.ts +++ b/contracts/tec/test/utils/tec_transaction_factory.ts @@ -1,31 +1,34 @@ -import { TransactionFactory } from '@0x/contracts-test-utils'; -import { SignatureType, SignedZeroExTransaction } from '@0x/types'; +import { generatePseudoRandomSalt } from '@0x/order-utils'; +import { SignedZeroExTransaction } from '@0x/types'; import * as ethUtil from 'ethereumjs-util'; -import { TECSignatureType } from './types'; +import { hashUtils, signingUtils, TECSignatureType } from './index'; -export class TECTransactionFactory extends TransactionFactory { - constructor(privateKey: Buffer, exchangeAddress: string) { - super(privateKey, exchangeAddress); +export class TECTransactionFactory { + private readonly _signerBuff: Buffer; + private readonly _verifyingContractAddress: string; + private readonly _privateKey: Buffer; + constructor(privateKey: Buffer, verifyingContractAddress: string) { + this._privateKey = privateKey; + this._verifyingContractAddress = verifyingContractAddress; + this._signerBuff = ethUtil.privateToAddress(this._privateKey); } public newSignedTECTransaction( data: string, signatureType: TECSignatureType = TECSignatureType.EthSign, ): SignedZeroExTransaction { - let exchangeSignatureType; - if (signatureType === TECSignatureType.EthSign) { - exchangeSignatureType = SignatureType.EthSign; - } else if (signatureType === TECSignatureType.EIP712) { - exchangeSignatureType = SignatureType.EIP712; - } else { - throw new Error(`Error: ${signatureType} not a valid signature type`); - } - const signedTransaction = super.newSignedTransaction(data, exchangeSignatureType); - const tecSignatureTypeByte = ethUtil.toBuffer(signatureType).toString('hex'); - signedTransaction.signature = `${signedTransaction.signature.slice( - 0, - signedTransaction.signature.length - 2, - )}${tecSignatureTypeByte}`; + const transaction = { + verifyingContractAddress: this._verifyingContractAddress, + signerAddress: ethUtil.addHexPrefix(this._signerBuff.toString('hex')), + salt: generatePseudoRandomSalt(), + data, + }; + const transactionHashBuff = hashUtils.getTransactionHashBuffer(transaction); + const signatureBuff = signingUtils.signMessage(transactionHashBuff, this._privateKey, signatureType); + const signedTransaction = { + ...transaction, + signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')), + }; return signedTransaction; } } diff --git a/contracts/tec/test/utils/types.ts b/contracts/tec/test/utils/types.ts index 499e64cc80..b913204c5c 100644 --- a/contracts/tec/test/utils/types.ts +++ b/contracts/tec/test/utils/types.ts @@ -6,6 +6,10 @@ export interface TECApproval { approvalExpirationTimeSeconds: BigNumber; } +export interface SignedTECApproval extends TECApproval { + approvalSignature: string; +} + export enum TECSignatureType { Illegal, EIP712, diff --git a/contracts/tec/tsconfig.json b/contracts/tec/tsconfig.json index b33be974cb..fc2b9eac21 100644 --- a/contracts/tec/tsconfig.json +++ b/contracts/tec/tsconfig.json @@ -3,9 +3,9 @@ "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ - "generated-artifacts/MixinSignatureValidator.json", "generated-artifacts/TEC.json", - "generated-artifacts/TestLibs.json" + "generated-artifacts/TestLibs.json", + "generated-artifacts/TestMixins.json" ], "exclude": ["./deploy/solc/solc_bin"] }