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"
},
"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": {

View File

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

View File

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

View File

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

View File

@ -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', () => {});
});

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 * 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;
},
};

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 { ApprovalFactory } from './approval_factory';
export { constants } from './constants';
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 { 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;
}
}

View File

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

View File

@ -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"]
}