Add ApprovalFactory class

This commit is contained in:
Amir Bandeali
2019-02-13 19:13:30 -08:00
parent bebcd99b3b
commit f409780455
14 changed files with 179 additions and 63 deletions

View File

@@ -16,5 +16,5 @@
} }
} }
}, },
"contracts": ["src/MixinSignatureValidator.sol", "src/TEC.sol", "test/TestLibs.sol"] "contracts": ["src/TEC.sol", "test/TestLibs.sol", "test/TestMixins.sol"]
} }

View File

@@ -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
{}

View File

@@ -33,7 +33,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
}, },
"config": { "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." "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
}, },
"repository": { "repository": {

View File

@@ -5,11 +5,11 @@
*/ */
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as MixinSignatureValidator from '../generated-artifacts/MixinSignatureValidator.json';
import * as TEC from '../generated-artifacts/TEC.json'; import * as TEC from '../generated-artifacts/TEC.json';
import * as TestLibs from '../generated-artifacts/TestLibs.json'; import * as TestLibs from '../generated-artifacts/TestLibs.json';
import * as TestMixins from '../generated-artifacts/TestMixins.json';
export const artifacts = { export const artifacts = {
TestMixins: TestMixins as ContractArtifact,
TEC: TEC as ContractArtifact, TEC: TEC as ContractArtifact,
TestLibs: TestLibs as ContractArtifact, TestLibs: TestLibs as ContractArtifact,
MixinSignatureValidator: MixinSignatureValidator as ContractArtifact,
}; };

View File

@@ -3,6 +3,6 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually. * 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/tec';
export * from '../generated-wrappers/test_libs'; export * from '../generated-wrappers/test_libs';
export * from '../generated-wrappers/test_mixins';

View File

@@ -3,7 +3,7 @@ import { BlockchainLifecycle } from '@0x/dev-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as chai from 'chai'; import * as chai from 'chai';
import { approvalHashUtils, artifacts, TestLibsContract } from '../src'; import { artifacts, hashUtils, TestLibsContract } from '../src';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
@@ -36,7 +36,7 @@ describe('Libs tests', () => {
signerAddress: constants.NULL_ADDRESS, signerAddress: constants.NULL_ADDRESS,
data: '0x1234', data: '0x1234',
}; };
const expectedTxHash = approvalHashUtils._getTransactionHashHex(tx); const expectedTxHash = hashUtils.getTransactionHashHex(tx);
const txHash = await testLibs.publicGetTransactionHash.callAsync(tx); const txHash = await testLibs.publicGetTransactionHash.callAsync(tx);
expect(expectedTxHash).to.eq(txHash); expect(expectedTxHash).to.eq(txHash);
}); });
@@ -53,11 +53,11 @@ describe('Libs tests', () => {
}; };
const approvalExpirationTimeSeconds = new BigNumber(0); const approvalExpirationTimeSeconds = new BigNumber(0);
const approval = { const approval = {
transactionHash: approvalHashUtils._getTransactionHashHex(signedTx), transactionHash: hashUtils.getTransactionHashHex(signedTx),
transactionSignature: signedTx.signature, transactionSignature: signedTx.signature,
approvalExpirationTimeSeconds, approvalExpirationTimeSeconds,
}; };
const expectedApprovalHash = approvalHashUtils.getApprovalHashHex(signedTx, approvalExpirationTimeSeconds); const expectedApprovalHash = hashUtils.getApprovalHashHex(signedTx, approvalExpirationTimeSeconds);
const approvalHash = await testLibs.publicGetTECApprovalHash.callAsync(approval); const approvalHash = await testLibs.publicGetTECApprovalHash.callAsync(approval);
expect(expectedApprovalHash).to.eq(approvalHash); expect(expectedApprovalHash).to.eq(approvalHash);
}); });

View File

@@ -7,13 +7,19 @@ import {
web3Wrapper, web3Wrapper,
} from '@0x/contracts-test-utils'; } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils'; import { BlockchainLifecycle } from '@0x/dev-utils';
import { transactionHashUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types';
import { RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as chai from 'chai'; import * as chai from 'chai';
import * as ethUtil from 'ethereumjs-util'; import * as ethUtil from 'ethereumjs-util';
import { artifacts, MixinSignatureValidatorContract, TECSignatureType, TECTransactionFactory } from '../src'; import {
ApprovalFactory,
artifacts,
hashUtils,
TECSignatureType,
TECTransactionFactory,
TestMixinsContract,
} from '../src';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
@@ -22,8 +28,10 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Mixins tests', () => { describe('Mixins tests', () => {
let transactionSignerAddress: string; let transactionSignerAddress: string;
let approvalSignerAddress: string; let approvalSignerAddress: string;
let signatureValidator: MixinSignatureValidatorContract; let mixins: TestMixinsContract;
let transactionFactory: TECTransactionFactory; let transactionFactory: TECTransactionFactory;
let approvalFactory: ApprovalFactory;
let defaultOrder: SignedOrder;
before(async () => { before(async () => {
await blockchainLifecycle.startAsync(); await blockchainLifecycle.startAsync();
@@ -32,16 +40,29 @@ describe('Mixins tests', () => {
await blockchainLifecycle.revertAsync(); await blockchainLifecycle.revertAsync();
}); });
before(async () => { before(async () => {
signatureValidator = await MixinSignatureValidatorContract.deployFrom0xArtifactAsync( mixins = await TestMixinsContract.deployFrom0xArtifactAsync(artifacts.TestMixins, provider, txDefaults);
artifacts.MixinSignatureValidator,
provider,
txDefaults,
);
const accounts = await web3Wrapper.getAvailableAddressesAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync();
[transactionSignerAddress, approvalSignerAddress] = accounts.slice(0, 2); [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 transactionSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[0];
const approvalSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[1]; 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 () => { beforeEach(async () => {
await blockchainLifecycle.startAsync(); await blockchainLifecycle.startAsync();
@@ -54,21 +75,15 @@ describe('Mixins tests', () => {
it('should return the correct address using the EthSign signature type', async () => { it('should return the correct address using the EthSign signature type', async () => {
const data = constants.NULL_BYTES; const data = constants.NULL_BYTES;
const transaction = transactionFactory.newSignedTECTransaction(data, TECSignatureType.EthSign); const transaction = transactionFactory.newSignedTECTransaction(data, TECSignatureType.EthSign);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = hashUtils.getTransactionHashHex(transaction);
const signerAddress = await signatureValidator.getSignerAddress.callAsync( const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature);
transactionHash,
transaction.signature,
);
expect(transaction.signerAddress).to.eq(signerAddress); expect(transaction.signerAddress).to.eq(signerAddress);
}); });
it('should return the correct address using the EIP712 signature type', async () => { it('should return the correct address using the EIP712 signature type', async () => {
const data = constants.NULL_BYTES; const data = constants.NULL_BYTES;
const transaction = transactionFactory.newSignedTECTransaction(data, TECSignatureType.EIP712); const transaction = transactionFactory.newSignedTECTransaction(data, TECSignatureType.EIP712);
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = hashUtils.getTransactionHashHex(transaction);
const signerAddress = await signatureValidator.getSignerAddress.callAsync( const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature);
transactionHash,
transaction.signature,
);
expect(transaction.signerAddress).to.eq(signerAddress); expect(transaction.signerAddress).to.eq(signerAddress);
}); });
it('should revert with with the Illegal signature type', async () => { it('should revert with with the Illegal signature type', async () => {
@@ -79,9 +94,9 @@ describe('Mixins tests', () => {
0, 0,
transaction.signature.length - 2, transaction.signature.length - 2,
)}${illegalSignatureByte}`; )}${illegalSignatureByte}`;
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = hashUtils.getTransactionHashHex(transaction);
expectContractCallFailedAsync( expectContractCallFailedAsync(
signatureValidator.getSignerAddress.callAsync(transactionHash, transaction.signature), mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
RevertReason.SignatureIllegal, RevertReason.SignatureIllegal,
); );
}); });
@@ -93,11 +108,15 @@ describe('Mixins tests', () => {
0, 0,
transaction.signature.length - 2, transaction.signature.length - 2,
)}${invalidSignatureByte}`; )}${invalidSignatureByte}`;
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = hashUtils.getTransactionHashHex(transaction);
expectContractCallFailedAsync( expectContractCallFailedAsync(
signatureValidator.getSignerAddress.callAsync(transactionHash, transaction.signature), mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
RevertReason.SignatureUnsupported, RevertReason.SignatureUnsupported,
); );
}); });
}); });
describe('assertValidSingleOrderApproval', () => {});
describe('assertValidBatchOrderApproval', () => {});
describe('assertValidTECApproval', () => {});
}); });

View File

@@ -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;
}
}

View File

@@ -4,16 +4,16 @@ import { SignedZeroExTransaction, ZeroExTransaction } from '@0x/types';
import { BigNumber, signTypedDataUtils } from '@0x/utils'; import { BigNumber, signTypedDataUtils } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { constants } from './constants'; import { constants } from './index';
export const approvalHashUtils = { export const hashUtils = {
getApprovalHashBuffer(transaction: SignedZeroExTransaction, approvalExpirationTimeSeconds: BigNumber): Buffer { getApprovalHashBuffer(transaction: SignedZeroExTransaction, approvalExpirationTimeSeconds: BigNumber): Buffer {
const domain = { const domain = {
name: constants.TEC_DOMAIN_NAME, name: constants.TEC_DOMAIN_NAME,
version: constants.TEC_DOMAIN_VERSION, version: constants.TEC_DOMAIN_VERSION,
verifyingContractAddress: transaction.verifyingContractAddress, verifyingContractAddress: transaction.verifyingContractAddress,
}; };
const transactionHash = approvalHashUtils._getTransactionHashHex(transaction); const transactionHash = hashUtils.getTransactionHashHex(transaction);
const approval = { const approval = {
transactionHash, transactionHash,
transactionSignature: transaction.signature, transactionSignature: transaction.signature,
@@ -31,12 +31,12 @@ export const approvalHashUtils = {
return hashBuffer; return hashBuffer;
}, },
getApprovalHashHex(transaction: SignedZeroExTransaction, approvalExpirationTimeSeconds: BigNumber): string { getApprovalHashHex(transaction: SignedZeroExTransaction, approvalExpirationTimeSeconds: BigNumber): string {
const hashHex = `0x${approvalHashUtils const hashHex = `0x${hashUtils
.getApprovalHashBuffer(transaction, approvalExpirationTimeSeconds) .getApprovalHashBuffer(transaction, approvalExpirationTimeSeconds)
.toString('hex')}`; .toString('hex')}`;
return hashHex; return hashHex;
}, },
_getTransactionHashBuffer(transaction: ZeroExTransaction | SignedZeroExTransaction): Buffer { getTransactionHashBuffer(transaction: ZeroExTransaction | SignedZeroExTransaction): Buffer {
const domain = { const domain = {
name: constants.TEC_DOMAIN_NAME, name: constants.TEC_DOMAIN_NAME,
version: constants.TEC_DOMAIN_VERSION, version: constants.TEC_DOMAIN_VERSION,
@@ -54,8 +54,8 @@ export const approvalHashUtils = {
const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData); const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
return hashBuffer; return hashBuffer;
}, },
_getTransactionHashHex(transaction: ZeroExTransaction | SignedZeroExTransaction): string { getTransactionHashHex(transaction: ZeroExTransaction | SignedZeroExTransaction): string {
const hashHex = `0x${approvalHashUtils._getTransactionHashBuffer(transaction).toString('hex')}`; const hashHex = `0x${hashUtils.getTransactionHashBuffer(transaction).toString('hex')}`;
return hashHex; return hashHex;
}, },
}; };

View File

@@ -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 { TECTransactionFactory } from './tec_transaction_factory';
export { ApprovalFactory } from './approval_factory';
export { constants } from './constants';
export * from './types'; export * from './types';

View File

@@ -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`);
}
},
};

View File

@@ -1,31 +1,34 @@
import { TransactionFactory } from '@0x/contracts-test-utils'; import { generatePseudoRandomSalt } from '@0x/order-utils';
import { SignatureType, SignedZeroExTransaction } from '@0x/types'; import { SignedZeroExTransaction } from '@0x/types';
import * as ethUtil from 'ethereumjs-util'; import * as ethUtil from 'ethereumjs-util';
import { TECSignatureType } from './types'; import { hashUtils, signingUtils, TECSignatureType } from './index';
export class TECTransactionFactory extends TransactionFactory { export class TECTransactionFactory {
constructor(privateKey: Buffer, exchangeAddress: string) { private readonly _signerBuff: Buffer;
super(privateKey, exchangeAddress); 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( public newSignedTECTransaction(
data: string, data: string,
signatureType: TECSignatureType = TECSignatureType.EthSign, signatureType: TECSignatureType = TECSignatureType.EthSign,
): SignedZeroExTransaction { ): SignedZeroExTransaction {
let exchangeSignatureType; const transaction = {
if (signatureType === TECSignatureType.EthSign) { verifyingContractAddress: this._verifyingContractAddress,
exchangeSignatureType = SignatureType.EthSign; signerAddress: ethUtil.addHexPrefix(this._signerBuff.toString('hex')),
} else if (signatureType === TECSignatureType.EIP712) { salt: generatePseudoRandomSalt(),
exchangeSignatureType = SignatureType.EIP712; data,
} else { };
throw new Error(`Error: ${signatureType} not a valid signature type`); const transactionHashBuff = hashUtils.getTransactionHashBuffer(transaction);
} const signatureBuff = signingUtils.signMessage(transactionHashBuff, this._privateKey, signatureType);
const signedTransaction = super.newSignedTransaction(data, exchangeSignatureType); const signedTransaction = {
const tecSignatureTypeByte = ethUtil.toBuffer(signatureType).toString('hex'); ...transaction,
signedTransaction.signature = `${signedTransaction.signature.slice( signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')),
0, };
signedTransaction.signature.length - 2,
)}${tecSignatureTypeByte}`;
return signedTransaction; return signedTransaction;
} }
} }

View File

@@ -6,6 +6,10 @@ export interface TECApproval {
approvalExpirationTimeSeconds: BigNumber; approvalExpirationTimeSeconds: BigNumber;
} }
export interface SignedTECApproval extends TECApproval {
approvalSignature: string;
}
export enum TECSignatureType { export enum TECSignatureType {
Illegal, Illegal,
EIP712, EIP712,

View File

@@ -3,9 +3,9 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [ "files": [
"generated-artifacts/MixinSignatureValidator.json",
"generated-artifacts/TEC.json", "generated-artifacts/TEC.json",
"generated-artifacts/TestLibs.json" "generated-artifacts/TestLibs.json",
"generated-artifacts/TestMixins.json"
], ],
"exclude": ["./deploy/solc/solc_bin"] "exclude": ["./deploy/solc/solc_bin"]
} }