Add mtx tooling to @0x/protocol-utils
(#90)
* `@0x/contracts-zero-ex`: Use the `MetaTransaction` class from `@0x/protocol-utils` in tests. * `@0x/protocol-utils`: Add the `MetaTransaction` class for EP mtxs + refactors * update changelogs * `@0x/protocol-utils`: Add mtx tests * `@0x/protocol-utils`: Rename `mtx.ts` to `meta_transactions.ts` and misc review feedback Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
602605ab4b
commit
682c07cb73
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "0.14.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Use the `MetaTransaction` class from `@0x/protocol-utils` in tests.",
|
||||
"pr": 90
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.13.0",
|
||||
"changes": [
|
||||
|
@ -61,6 +61,7 @@
|
||||
"@0x/contracts-gen": "^2.0.24",
|
||||
"@0x/contracts-test-utils": "^5.3.15",
|
||||
"@0x/dev-utils": "^4.1.3",
|
||||
"@0x/order-utils": "^10.4.10",
|
||||
"@0x/sol-compiler": "^4.4.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
@ -82,7 +83,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.14",
|
||||
"@0x/order-utils": "^10.4.10",
|
||||
"@0x/protocol-utils": "^1.0.1",
|
||||
"@0x/subproviders": "^6.2.3",
|
||||
"@0x/types": "^3.3.1",
|
||||
|
@ -6,9 +6,7 @@ import {
|
||||
randomAddress,
|
||||
verifyEventsFromLogs,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { getExchangeProxyMetaTransactionHash, signatureUtils } from '@0x/order-utils';
|
||||
import { Signature } from '@0x/protocol-utils';
|
||||
import { ExchangeProxyMetaTransaction } from '@0x/types';
|
||||
import { MetaTransaction, MetaTransactionFields } from '@0x/protocol-utils';
|
||||
import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@ -31,6 +29,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
let owner: string;
|
||||
let maker: string;
|
||||
let sender: string;
|
||||
let notSigner: string;
|
||||
let signers: string[];
|
||||
let zeroEx: IZeroExContract;
|
||||
let feature: MetaTransactionsFeatureContract;
|
||||
@ -45,7 +44,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
const REENTRANCY_FLAG_MTX = 0x1;
|
||||
|
||||
before(async () => {
|
||||
[owner, maker, sender, ...signers] = await env.getAccountAddressesAsync();
|
||||
[owner, maker, sender, notSigner, ...signers] = await env.getAccountAddressesAsync();
|
||||
transformERC20Feature = await TestMetaTransactionsTransformERC20FeatureContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMetaTransactionsTransformERC20Feature,
|
||||
env.provider,
|
||||
@ -83,19 +82,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
);
|
||||
});
|
||||
|
||||
function sigstruct(signature: string): Signature {
|
||||
return {
|
||||
v: parseInt(hexUtils.slice(signature, 0, 1), 16),
|
||||
signatureType: parseInt(hexUtils.slice(signature, 65, 66), 16),
|
||||
r: hexUtils.slice(signature, 1, 33),
|
||||
s: hexUtils.slice(signature, 33, 65),
|
||||
};
|
||||
}
|
||||
|
||||
function getRandomMetaTransaction(
|
||||
fields: Partial<ExchangeProxyMetaTransaction> = {},
|
||||
): ExchangeProxyMetaTransaction {
|
||||
return {
|
||||
function getRandomMetaTransaction(fields: Partial<MetaTransactionFields> = {}): MetaTransaction {
|
||||
return new MetaTransaction({
|
||||
signer: _.sampleSize(signers)[0],
|
||||
sender,
|
||||
minGasPrice: getRandomInteger('2', '1e9'),
|
||||
@ -106,28 +94,16 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
value: getRandomInteger(1, '1e18'),
|
||||
feeToken: feeToken.address,
|
||||
feeAmount: getRandomInteger(1, MAX_FEE_AMOUNT),
|
||||
domain: {
|
||||
chainId: 1, // Ganache's `chainid` opcode is hardcoded as 1
|
||||
verifyingContract: zeroEx.address,
|
||||
},
|
||||
chainId: 1, // Ganache's `chainid` opcode is hardcoded as 1
|
||||
verifyingContract: zeroEx.address,
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
async function signMetaTransactionAsync(mtx: ExchangeProxyMetaTransaction, signer?: string): Promise<Signature> {
|
||||
return sigstruct(
|
||||
await signatureUtils.ecSignHashAsync(
|
||||
env.provider,
|
||||
getExchangeProxyMetaTransactionHash(mtx),
|
||||
signer || mtx.signer,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
describe('getMetaTransactionHash()', () => {
|
||||
it('generates the correct hash', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const expected = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const expected = mtx.getHash();
|
||||
const actual = await feature.getMetaTransactionHash(mtx).callAsync();
|
||||
expect(actual).to.eq(expected);
|
||||
});
|
||||
@ -163,7 +139,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: nativeOrdersFeature.fillLimitOrder(order, sig, fillAmount).getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -198,7 +174,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
callData: nativeOrdersFeature.fillRfqOrder(order, sig, fillAmount).getABIEncodedTransactionData(),
|
||||
value: ZERO_AMOUNT,
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: 0,
|
||||
@ -237,7 +213,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -275,7 +251,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData();
|
||||
const mtx = getRandomMetaTransaction({ callData });
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -315,7 +291,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -340,7 +316,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -363,8 +339,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -393,8 +369,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
const mtx = getRandomMetaTransaction({
|
||||
callData: transformERC20Feature.createTransformWallet().getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -421,8 +397,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -439,8 +415,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
|
||||
it('fails if not enough ETH provided', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value.minus(1),
|
||||
@ -457,8 +433,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
|
||||
it('fails if gas price too low', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice.minus(1),
|
||||
value: mtx.value,
|
||||
@ -476,8 +452,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
|
||||
it('fails if gas price too high', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice.plus(1),
|
||||
value: mtx.value,
|
||||
@ -497,8 +473,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
const mtx = getRandomMetaTransaction({
|
||||
expirationTimeSeconds: new BigNumber(Math.floor(_.now() / 1000 - 60)),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -518,8 +494,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
const mtx = getRandomMetaTransaction({
|
||||
sender: requiredSender,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -536,8 +512,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
|
||||
it('fails if signature is wrong', async () => {
|
||||
const mtx = getRandomMetaTransaction({ signer: signers[0] });
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx, signers[1]);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.clone({ signer: notSigner }).getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -567,8 +543,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -600,8 +576,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_BATCH_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -633,8 +609,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -666,8 +642,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_BATCH_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -703,7 +679,9 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
});
|
||||
const signatures = await Promise.all(mtxs.map(async mtx => signMetaTransactionAsync(mtx)));
|
||||
const signatures = await Promise.all(
|
||||
mtxs.map(async mtx => mtx.getSignatureWithProviderAsync(env.provider)),
|
||||
);
|
||||
const callOpts = {
|
||||
gasPrice: BigNumber.max(...mtxs.map(mtx => mtx.minGasPrice)),
|
||||
value: BigNumber.sum(...mtxs.map(mtx => mtx.value)),
|
||||
@ -728,9 +706,9 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
})();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const mtxs = _.times(2, () => mtx);
|
||||
const signatures = await Promise.all(mtxs.map(async m => signMetaTransactionAsync(m)));
|
||||
const signatures = await Promise.all(mtxs.map(async m => m.getSignatureWithProviderAsync(env.provider)));
|
||||
const callOpts = {
|
||||
gasPrice: BigNumber.max(...mtxs.map(m => m.minGasPrice)),
|
||||
value: BigNumber.sum(...mtxs.map(m => m.value)),
|
||||
@ -756,8 +734,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -786,8 +764,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -819,8 +797,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
.getABIEncodedTransactionData(),
|
||||
value: TRANSFORM_ERC20_BATCH_REENTER_VALUE,
|
||||
});
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.maxGasPrice,
|
||||
value: mtx.value,
|
||||
@ -859,7 +837,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
@ -873,7 +851,7 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
describe('getMetaTransactionHashExecutedBlock()', () => {
|
||||
it('returns zero for an unexecuted mtx', async () => {
|
||||
const mtx = getRandomMetaTransaction();
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const block = await feature.getMetaTransactionHashExecutedBlock(mtxHash).callAsync();
|
||||
expect(block).to.bignumber.eq(0);
|
||||
});
|
||||
@ -891,13 +869,13 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
||||
)
|
||||
.getABIEncodedTransactionData(),
|
||||
});
|
||||
const signature = await signMetaTransactionAsync(mtx);
|
||||
const signature = await mtx.getSignatureWithProviderAsync(env.provider);
|
||||
const callOpts = {
|
||||
gasPrice: mtx.minGasPrice,
|
||||
value: mtx.value,
|
||||
};
|
||||
const receipt = await feature.executeMetaTransaction(mtx, signature).awaitTransactionSuccessAsync(callOpts);
|
||||
const mtxHash = getExchangeProxyMetaTransactionHash(mtx);
|
||||
const mtxHash = mtx.getHash();
|
||||
const block = await feature.getMetaTransactionHashExecutedBlock(mtxHash).callAsync();
|
||||
expect(block).to.bignumber.eq(receipt.blockNumber);
|
||||
});
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add the `MetaTransaction` class for EP mtxs",
|
||||
"pr": 90
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1607485227,
|
||||
"version": "1.0.1",
|
||||
|
@ -1 +1,4 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||
export const ZERO = new BigNumber(0);
|
||||
|
@ -7,6 +7,8 @@ export interface EIP712Domain {
|
||||
verifyingContract: string;
|
||||
}
|
||||
|
||||
export type EIP712_STRUCT_ABI = Array<{ type: string; name: string }>;
|
||||
|
||||
export const EIP712_DOMAIN_PARAMETERS = [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'version', type: 'string' },
|
||||
@ -68,3 +70,12 @@ export function getExchangeProxyEIP712Hash(structHash: string, chainId?: number,
|
||||
hexUtils.concat('0x1901', getExchangeProxyEIP712DomainHash(chainId, verifyingContract), structHash),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the type hash of an EIP712 struct given its ABI.
|
||||
*/
|
||||
export function getTypeHash(structName: string, abi: EIP712_STRUCT_ABI): string {
|
||||
return hexUtils.hash(
|
||||
hexUtils.toHex(Buffer.from([`${structName}(`, abi.map(a => `${a.type} ${a.name}`).join(','), ')'].join(''))),
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ export const RevertError = _RevertErrors.RevertError;
|
||||
|
||||
export * from './eip712_utils';
|
||||
export * from './orders';
|
||||
export * from './meta_transactions';
|
||||
export * from './signature_utils';
|
||||
export * from './transformer_utils';
|
||||
export * from './constants';
|
||||
|
171
packages/protocol-utils/src/meta_transactions.ts
Normal file
171
packages/protocol-utils/src/meta_transactions.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { SupportedProvider } from '@0x/subproviders';
|
||||
import { EIP712TypedData } from '@0x/types';
|
||||
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||
|
||||
import { ZERO } from './constants';
|
||||
import {
|
||||
createExchangeProxyEIP712Domain,
|
||||
EIP712_DOMAIN_PARAMETERS,
|
||||
getExchangeProxyEIP712Hash,
|
||||
getTypeHash,
|
||||
} from './eip712_utils';
|
||||
import {
|
||||
eip712SignTypedDataWithKey,
|
||||
eip712SignTypedDataWithProviderAsync,
|
||||
ethSignHashWithKey,
|
||||
ethSignHashWithProviderAsync,
|
||||
Signature,
|
||||
SignatureType,
|
||||
} from './signature_utils';
|
||||
|
||||
const MTX_DEFAULT_VALUES = {
|
||||
signer: NULL_ADDRESS,
|
||||
sender: NULL_ADDRESS,
|
||||
minGasPrice: ZERO,
|
||||
maxGasPrice: ZERO,
|
||||
expirationTimeSeconds: ZERO,
|
||||
salt: ZERO,
|
||||
callData: hexUtils.leftPad(0),
|
||||
value: ZERO,
|
||||
feeToken: NULL_ADDRESS,
|
||||
feeAmount: ZERO,
|
||||
chainId: 1,
|
||||
verifyingContract: getContractAddressesForChainOrThrow(1).exchangeProxy,
|
||||
};
|
||||
|
||||
export type MetaTransactionFields = typeof MTX_DEFAULT_VALUES;
|
||||
|
||||
export class MetaTransaction {
|
||||
public static readonly STRUCT_NAME = 'MetaTransactionData';
|
||||
public static readonly STRUCT_ABI = [
|
||||
{ type: 'address', name: 'signer' },
|
||||
{ type: 'address', name: 'sender' },
|
||||
{ type: 'uint256', name: 'minGasPrice' },
|
||||
{ type: 'uint256', name: 'maxGasPrice' },
|
||||
{ type: 'uint256', name: 'expirationTimeSeconds' },
|
||||
{ type: 'uint256', name: 'salt' },
|
||||
{ type: 'bytes', name: 'callData' },
|
||||
{ type: 'uint256', name: 'value' },
|
||||
{ type: 'address', name: 'feeToken' },
|
||||
{ type: 'uint256', name: 'feeAmount' },
|
||||
];
|
||||
public static readonly TYPE_HASH = getTypeHash(MetaTransaction.STRUCT_NAME, MetaTransaction.STRUCT_ABI);
|
||||
|
||||
public signer: string;
|
||||
public sender: string;
|
||||
public minGasPrice: BigNumber;
|
||||
public maxGasPrice: BigNumber;
|
||||
public expirationTimeSeconds: BigNumber;
|
||||
public salt: BigNumber;
|
||||
public callData: string;
|
||||
public value: BigNumber;
|
||||
public feeToken: string;
|
||||
public feeAmount: BigNumber;
|
||||
public chainId: number;
|
||||
public verifyingContract: string;
|
||||
|
||||
public constructor(fields: Partial<MetaTransactionFields> = {}) {
|
||||
const _fields = { ...MTX_DEFAULT_VALUES, ...fields };
|
||||
this.signer = _fields.signer;
|
||||
this.sender = _fields.sender;
|
||||
this.minGasPrice = _fields.minGasPrice;
|
||||
this.maxGasPrice = _fields.maxGasPrice;
|
||||
this.expirationTimeSeconds = _fields.expirationTimeSeconds;
|
||||
this.salt = _fields.salt;
|
||||
this.callData = _fields.callData;
|
||||
this.value = _fields.value;
|
||||
this.feeToken = _fields.feeToken;
|
||||
this.feeAmount = _fields.feeAmount;
|
||||
this.chainId = _fields.chainId;
|
||||
this.verifyingContract = _fields.verifyingContract;
|
||||
}
|
||||
|
||||
public clone(fields: Partial<MetaTransactionFields> = {}): MetaTransaction {
|
||||
return new MetaTransaction({
|
||||
signer: this.signer,
|
||||
sender: this.sender,
|
||||
minGasPrice: this.minGasPrice,
|
||||
maxGasPrice: this.maxGasPrice,
|
||||
expirationTimeSeconds: this.expirationTimeSeconds,
|
||||
salt: this.salt,
|
||||
callData: this.callData,
|
||||
value: this.value,
|
||||
feeToken: this.feeToken,
|
||||
feeAmount: this.feeAmount,
|
||||
chainId: this.chainId,
|
||||
verifyingContract: this.verifyingContract,
|
||||
...fields,
|
||||
});
|
||||
}
|
||||
|
||||
public getStructHash(): string {
|
||||
return hexUtils.hash(
|
||||
hexUtils.concat(
|
||||
hexUtils.leftPad(MetaTransaction.TYPE_HASH),
|
||||
hexUtils.leftPad(this.signer),
|
||||
hexUtils.leftPad(this.sender),
|
||||
hexUtils.leftPad(this.minGasPrice),
|
||||
hexUtils.leftPad(this.maxGasPrice),
|
||||
hexUtils.leftPad(this.expirationTimeSeconds),
|
||||
hexUtils.leftPad(this.salt),
|
||||
hexUtils.hash(this.callData),
|
||||
hexUtils.leftPad(this.value),
|
||||
hexUtils.leftPad(this.feeToken),
|
||||
hexUtils.leftPad(this.feeAmount),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public getEIP712TypedData(): EIP712TypedData {
|
||||
return {
|
||||
types: {
|
||||
EIP712Domain: EIP712_DOMAIN_PARAMETERS,
|
||||
[MetaTransaction.STRUCT_NAME]: MetaTransaction.STRUCT_ABI,
|
||||
},
|
||||
domain: createExchangeProxyEIP712Domain(this.chainId, this.verifyingContract) as any,
|
||||
primaryType: MetaTransaction.STRUCT_NAME,
|
||||
message: {
|
||||
signer: this.signer,
|
||||
sender: this.sender,
|
||||
minGasPrice: this.minGasPrice.toString(10),
|
||||
maxGasPrice: this.maxGasPrice.toString(10),
|
||||
expirationTimeSeconds: this.expirationTimeSeconds.toString(10),
|
||||
salt: this.salt.toString(10),
|
||||
callData: this.callData,
|
||||
value: this.value.toString(10),
|
||||
feeToken: this.feeToken,
|
||||
feeAmount: this.feeAmount.toString(10),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public getHash(): string {
|
||||
return getExchangeProxyEIP712Hash(this.getStructHash(), this.chainId, this.verifyingContract);
|
||||
}
|
||||
|
||||
public async getSignatureWithProviderAsync(
|
||||
provider: SupportedProvider,
|
||||
type: SignatureType = SignatureType.EthSign,
|
||||
): Promise<Signature> {
|
||||
switch (type) {
|
||||
case SignatureType.EIP712:
|
||||
return eip712SignTypedDataWithProviderAsync(this.getEIP712TypedData(), this.signer, provider);
|
||||
case SignatureType.EthSign:
|
||||
return ethSignHashWithProviderAsync(this.getHash(), this.signer, provider);
|
||||
default:
|
||||
throw new Error(`Cannot sign with signature type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getSignatureWithKey(key: string, type: SignatureType = SignatureType.EthSign): Signature {
|
||||
switch (type) {
|
||||
case SignatureType.EIP712:
|
||||
return eip712SignTypedDataWithKey(this.getEIP712TypedData(), key);
|
||||
case SignatureType.EthSign:
|
||||
return ethSignHashWithKey(this.getHash(), key);
|
||||
default:
|
||||
throw new Error(`Cannot sign with signature type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,15 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { SupportedProvider } from '@0x/subproviders';
|
||||
import { EIP712TypedData } from '@0x/types';
|
||||
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
|
||||
|
||||
import { createExchangeProxyEIP712Domain, EIP712_DOMAIN_PARAMETERS, getExchangeProxyEIP712Hash } from './eip712_utils';
|
||||
import { ZERO } from './constants';
|
||||
import {
|
||||
createExchangeProxyEIP712Domain,
|
||||
EIP712_DOMAIN_PARAMETERS,
|
||||
getExchangeProxyEIP712Hash,
|
||||
getTypeHash,
|
||||
} from './eip712_utils';
|
||||
import {
|
||||
eip712SignTypedDataWithKey,
|
||||
eip712SignTypedDataWithProviderAsync,
|
||||
@ -12,7 +19,6 @@ import {
|
||||
SignatureType,
|
||||
} from './signature_utils';
|
||||
|
||||
const ZERO = new BigNumber(0);
|
||||
const COMMON_ORDER_DEFAULT_VALUES = {
|
||||
makerToken: NULL_ADDRESS,
|
||||
takerToken: NULL_ADDRESS,
|
||||
@ -24,7 +30,7 @@ const COMMON_ORDER_DEFAULT_VALUES = {
|
||||
expiry: ZERO,
|
||||
salt: ZERO,
|
||||
chainId: 1,
|
||||
verifyingContract: '0xdef1c0ded9bec7f1a1670819833240f027b25eff',
|
||||
verifyingContract: getContractAddressesForChainOrThrow(1).exchangeProxy,
|
||||
};
|
||||
const LIMIT_ORDER_DEFAULT_VALUES = {
|
||||
...COMMON_ORDER_DEFAULT_VALUES,
|
||||
@ -117,30 +123,22 @@ export abstract class OrderBase {
|
||||
}
|
||||
|
||||
export class LimitOrder extends OrderBase {
|
||||
public static readonly TYPE_HASH = hexUtils.hash(
|
||||
hexUtils.toHex(
|
||||
Buffer.from(
|
||||
[
|
||||
'LimitOrder(',
|
||||
[
|
||||
'address makerToken',
|
||||
'address takerToken',
|
||||
'uint128 makerAmount',
|
||||
'uint128 takerAmount',
|
||||
'uint128 takerTokenFeeAmount',
|
||||
'address maker',
|
||||
'address taker',
|
||||
'address sender',
|
||||
'address feeRecipient',
|
||||
'bytes32 pool',
|
||||
'uint64 expiry',
|
||||
'uint256 salt',
|
||||
].join(','),
|
||||
')',
|
||||
].join(''),
|
||||
),
|
||||
),
|
||||
);
|
||||
public static readonly STRUCT_NAME = 'LimitOrder';
|
||||
public static readonly STRUCT_ABI = [
|
||||
{ type: 'address', name: 'makerToken' },
|
||||
{ type: 'address', name: 'takerToken' },
|
||||
{ type: 'uint128', name: 'makerAmount' },
|
||||
{ type: 'uint128', name: 'takerAmount' },
|
||||
{ type: 'uint128', name: 'takerTokenFeeAmount' },
|
||||
{ type: 'address', name: 'maker' },
|
||||
{ type: 'address', name: 'taker' },
|
||||
{ type: 'address', name: 'sender' },
|
||||
{ type: 'address', name: 'feeRecipient' },
|
||||
{ type: 'bytes32', name: 'pool' },
|
||||
{ type: 'uint64', name: 'expiry' },
|
||||
{ type: 'uint256', name: 'salt' },
|
||||
];
|
||||
public static readonly TYPE_HASH = getTypeHash(LimitOrder.STRUCT_NAME, LimitOrder.STRUCT_ABI);
|
||||
|
||||
public takerTokenFeeAmount: BigNumber;
|
||||
public sender: string;
|
||||
@ -198,23 +196,10 @@ export class LimitOrder extends OrderBase {
|
||||
return {
|
||||
types: {
|
||||
EIP712Domain: EIP712_DOMAIN_PARAMETERS,
|
||||
LimitOrder: [
|
||||
{ type: 'address', name: 'makerToken' },
|
||||
{ type: 'address', name: 'takerToken' },
|
||||
{ type: 'uint128', name: 'makerAmount' },
|
||||
{ type: 'uint128', name: 'takerAmount' },
|
||||
{ type: 'uint128', name: 'takerTokenFeeAmount' },
|
||||
{ type: 'address', name: 'maker' },
|
||||
{ type: 'address', name: 'taker' },
|
||||
{ type: 'address', name: 'sender' },
|
||||
{ type: 'address', name: 'feeRecipient' },
|
||||
{ type: 'bytes32', name: 'pool' },
|
||||
{ type: 'uint64', name: 'expiry' },
|
||||
{ type: 'uint256', name: 'salt' },
|
||||
],
|
||||
[LimitOrder.STRUCT_NAME]: LimitOrder.STRUCT_ABI,
|
||||
},
|
||||
domain: createExchangeProxyEIP712Domain(this.chainId, this.verifyingContract) as any,
|
||||
primaryType: 'LimitOrder',
|
||||
primaryType: LimitOrder.STRUCT_NAME,
|
||||
message: {
|
||||
makerToken: this.makerToken,
|
||||
takerToken: this.takerToken,
|
||||
@ -234,28 +219,20 @@ export class LimitOrder extends OrderBase {
|
||||
}
|
||||
|
||||
export class RfqOrder extends OrderBase {
|
||||
public static readonly TYPE_HASH = hexUtils.hash(
|
||||
hexUtils.toHex(
|
||||
Buffer.from(
|
||||
[
|
||||
'RfqOrder(',
|
||||
[
|
||||
'address makerToken',
|
||||
'address takerToken',
|
||||
'uint128 makerAmount',
|
||||
'uint128 takerAmount',
|
||||
'address maker',
|
||||
'address taker',
|
||||
'address txOrigin',
|
||||
'bytes32 pool',
|
||||
'uint64 expiry',
|
||||
'uint256 salt',
|
||||
].join(','),
|
||||
')',
|
||||
].join(''),
|
||||
),
|
||||
),
|
||||
);
|
||||
public static readonly STRUCT_NAME = 'RfqOrder';
|
||||
public static readonly STRUCT_ABI = [
|
||||
{ type: 'address', name: 'makerToken' },
|
||||
{ type: 'address', name: 'takerToken' },
|
||||
{ type: 'uint128', name: 'makerAmount' },
|
||||
{ type: 'uint128', name: 'takerAmount' },
|
||||
{ type: 'address', name: 'maker' },
|
||||
{ type: 'address', name: 'taker' },
|
||||
{ type: 'address', name: 'txOrigin' },
|
||||
{ type: 'bytes32', name: 'pool' },
|
||||
{ type: 'uint64', name: 'expiry' },
|
||||
{ type: 'uint256', name: 'salt' },
|
||||
];
|
||||
public static readonly TYPE_HASH = getTypeHash(RfqOrder.STRUCT_NAME, RfqOrder.STRUCT_ABI);
|
||||
|
||||
public txOrigin: string;
|
||||
|
||||
@ -305,21 +282,10 @@ export class RfqOrder extends OrderBase {
|
||||
return {
|
||||
types: {
|
||||
EIP712Domain: EIP712_DOMAIN_PARAMETERS,
|
||||
RfqOrder: [
|
||||
{ type: 'address', name: 'makerToken' },
|
||||
{ type: 'address', name: 'takerToken' },
|
||||
{ type: 'uint128', name: 'makerAmount' },
|
||||
{ type: 'uint128', name: 'takerAmount' },
|
||||
{ type: 'address', name: 'maker' },
|
||||
{ type: 'address', name: 'taker' },
|
||||
{ type: 'address', name: 'txOrigin' },
|
||||
{ type: 'bytes32', name: 'pool' },
|
||||
{ type: 'uint64', name: 'expiry' },
|
||||
{ type: 'uint256', name: 'salt' },
|
||||
],
|
||||
[RfqOrder.STRUCT_NAME]: RfqOrder.STRUCT_ABI,
|
||||
},
|
||||
domain: createExchangeProxyEIP712Domain(this.chainId, this.verifyingContract) as any,
|
||||
primaryType: 'RfqOrder',
|
||||
primaryType: RfqOrder.STRUCT_NAME,
|
||||
message: {
|
||||
makerToken: this.makerToken,
|
||||
takerToken: this.takerToken,
|
||||
|
84
packages/protocol-utils/test/meta_transactions_test.ts
Normal file
84
packages/protocol-utils/test/meta_transactions_test.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { chaiSetup, web3Factory, Web3Wrapper } from '@0x/dev-utils';
|
||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { expect } from 'chai';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
import { MetaTransaction } from '../src/meta_transactions';
|
||||
import { SignatureType } from '../src/signature_utils';
|
||||
|
||||
chaiSetup.configure();
|
||||
|
||||
describe('mtxs', () => {
|
||||
let provider: Web3ProviderEngine;
|
||||
let providerMaker: string;
|
||||
const key = '0xee094b79aa0315914955f2f09be9abe541dcdc51f0aae5bec5453e9f73a471a6';
|
||||
const keyMaker = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(key)));
|
||||
|
||||
before(async () => {
|
||||
provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||
[providerMaker] = await new Web3Wrapper(provider).getAvailableAddressesAsync();
|
||||
});
|
||||
|
||||
describe('MetaTransaction', () => {
|
||||
const mtx = new MetaTransaction({
|
||||
signer: '0x349e8d89e8b37214d9ce3949fc5754152c525bc3',
|
||||
sender: '0x83c62b2e67dea0df2a27be0def7a22bd7102642c',
|
||||
minGasPrice: new BigNumber(1234),
|
||||
maxGasPrice: new BigNumber(5678),
|
||||
expirationTimeSeconds: new BigNumber(9101112),
|
||||
salt: new BigNumber(2001),
|
||||
callData: '0x12345678',
|
||||
value: new BigNumber(1001),
|
||||
feeToken: '0xcc3c7ea403427154ec908203ba6c418bd699f7ce',
|
||||
feeAmount: new BigNumber(9101112),
|
||||
chainId: 8008,
|
||||
verifyingContract: '0x6701704d2421c64ee9aa93ec7f96ede81c4be77d',
|
||||
});
|
||||
|
||||
it('can get the struct hash', () => {
|
||||
const actual = mtx.getStructHash();
|
||||
const expected = '0x164b8bfaed3718d233d4cc87501d0d8fa0a72ed7deeb8e591524133f17867180';
|
||||
expect(actual).to.eq(expected);
|
||||
});
|
||||
|
||||
it('can get the EIP712 hash', () => {
|
||||
const actual = mtx.getHash();
|
||||
const expected = '0x068f2f98836e489070608461768bfd3331128787d09278d38869c2b56bfc34a4';
|
||||
expect(actual).to.eq(expected);
|
||||
});
|
||||
|
||||
it('can get an EthSign signature with a provider', async () => {
|
||||
const actual = await mtx.clone({ signer: providerMaker }).getSignatureWithProviderAsync(provider);
|
||||
const expected = {
|
||||
signatureType: SignatureType.EthSign,
|
||||
r: '0xbb831776a2d6639d4e4d1641f158773ce202881bac74dddb2672d5ff5521ef5c',
|
||||
s: '0x746a61ccfdfee3afae15f4a3bd67ded2ce555d89d482940a844eeffaede2ee8a',
|
||||
v: 27,
|
||||
};
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
|
||||
it('can get an EthSign signature with a private key', () => {
|
||||
const actual = mtx.clone({ signer: keyMaker }).getSignatureWithKey(key);
|
||||
const expected = {
|
||||
signatureType: SignatureType.EthSign,
|
||||
r: '0xbf19b5ef62df8c8315727087e9d8562e3b88d32452ac8193e3ed9f5354a220ef',
|
||||
s: '0x512387e81b2c03e4bc4cf72ee5293c86498c17fde3ae89f18dd0705076a7f472',
|
||||
v: 28,
|
||||
};
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
|
||||
it('can get an EIP712 signature with a private key', () => {
|
||||
const actual = mtx.clone({ signer: keyMaker }).getSignatureWithKey(key, SignatureType.EIP712);
|
||||
const expected = {
|
||||
signatureType: SignatureType.EIP712,
|
||||
r: '0x050c6b80a3fafa1b816fdfd646f3e90862a21d3fbf3ed675eaf9c89e092ec405',
|
||||
s: '0x179600bd412820233598628b85b58f1e9f6da4555421f45266ec2ebf94153d1d',
|
||||
v: 27,
|
||||
};
|
||||
expect(actual).to.deep.eq(expected);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user