Exchange signature validation fuzz tests (#2425)
* `@0x/contracts-integrations`: Add Exchange signature validation fuzz tests. * `@0x/contracts-integrations`: Switch from actor pattern to just pure function generators. Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
8d10736934
commit
de12da18da
@ -9,6 +9,10 @@
|
||||
{
|
||||
"note": "Add aggregator mainnet tests.",
|
||||
"pr": 2407
|
||||
},
|
||||
{
|
||||
"note": "Add fuzz tests for Exchange signature validation.",
|
||||
"pr": 2425
|
||||
}
|
||||
],
|
||||
"timestamp": 1578272714
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibEIP1271.sol";
|
||||
|
||||
|
||||
contract TestSignatureValidationWallet is
|
||||
LibEIP1271
|
||||
{
|
||||
bytes4 private constant LEGACY_WALLET_MAGIC_VALUE = 0xb0671381;
|
||||
|
||||
// Callback used by `EIP1271Wallet` and `Validator` signature types.
|
||||
function isValidSignature(
|
||||
bytes memory,
|
||||
bytes memory
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (bytes4 magicValue)
|
||||
{
|
||||
return EIP1271_MAGIC_VALUE;
|
||||
}
|
||||
|
||||
// Callback used by `Wallet` signature type.
|
||||
function isValidSignature(
|
||||
bytes32,
|
||||
bytes memory
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (bytes4 magicValue)
|
||||
{
|
||||
return LEGACY_WALLET_MAGIC_VALUE;
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "TestFramework",
|
||||
"abis": "./test/generated-artifacts/@(TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||
"abis": "./test/generated-artifacts/@(TestDydxUser|TestEth2Dai|TestEth2DaiBridge|TestFramework|TestMainnetAggregatorFills|TestSignatureValidationWallet|TestUniswapBridge|TestUniswapExchange|TestUniswapExchangeFactory).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
|
@ -10,6 +10,7 @@ import * as TestEth2Dai from '../test/generated-artifacts/TestEth2Dai.json';
|
||||
import * as TestEth2DaiBridge from '../test/generated-artifacts/TestEth2DaiBridge.json';
|
||||
import * as TestFramework from '../test/generated-artifacts/TestFramework.json';
|
||||
import * as TestMainnetAggregatorFills from '../test/generated-artifacts/TestMainnetAggregatorFills.json';
|
||||
import * as TestSignatureValidationWallet from '../test/generated-artifacts/TestSignatureValidationWallet.json';
|
||||
import * as TestUniswapBridge from '../test/generated-artifacts/TestUniswapBridge.json';
|
||||
import * as TestUniswapExchange from '../test/generated-artifacts/TestUniswapExchange.json';
|
||||
import * as TestUniswapExchangeFactory from '../test/generated-artifacts/TestUniswapExchangeFactory.json';
|
||||
@ -19,6 +20,7 @@ export const artifacts = {
|
||||
TestEth2DaiBridge: TestEth2DaiBridge as ContractArtifact,
|
||||
TestFramework: TestFramework as ContractArtifact,
|
||||
TestMainnetAggregatorFills: TestMainnetAggregatorFills as ContractArtifact,
|
||||
TestSignatureValidationWallet: TestSignatureValidationWallet as ContractArtifact,
|
||||
TestUniswapBridge: TestUniswapBridge as ContractArtifact,
|
||||
TestUniswapExchange: TestUniswapExchange as ContractArtifact,
|
||||
TestUniswapExchangeFactory: TestUniswapExchangeFactory as ContractArtifact,
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
} from '@0x/contracts-staking';
|
||||
import { BlockchainTestsEnvironment, constants } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@ -207,6 +208,7 @@ export class DeploymentManager {
|
||||
|
||||
// Construct the new instance and return it.
|
||||
return new DeploymentManager(
|
||||
environment.web3Wrapper,
|
||||
assetProxies,
|
||||
governor,
|
||||
exchange,
|
||||
@ -522,6 +524,7 @@ export class DeploymentManager {
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
public web3Wrapper: Web3Wrapper,
|
||||
public assetProxies: AssetProxyContracts,
|
||||
public governor: ZeroExGovernorContract,
|
||||
public exchange: ExchangeContract,
|
||||
|
@ -48,6 +48,7 @@ export class SimulationEnvironment {
|
||||
|
||||
export abstract class Simulation {
|
||||
public readonly generator = this._assertionGenerator();
|
||||
public resets = false;
|
||||
|
||||
constructor(public environment: SimulationEnvironment) {}
|
||||
|
||||
@ -66,11 +67,15 @@ export abstract class Simulation {
|
||||
protected abstract _assertionGenerator(): AsyncIterableIterator<AssertionResult | void>;
|
||||
|
||||
private async _stepAsync(): Promise<void> {
|
||||
const snapshotId = this.resets ? await this.environment.deployment.web3Wrapper.takeSnapshotAsync() : undefined;
|
||||
try {
|
||||
await this.generator.next();
|
||||
} catch (error) {
|
||||
logger.logFailure(error, this.environment.state());
|
||||
throw error;
|
||||
}
|
||||
if (snapshotId !== undefined) {
|
||||
await this.environment.deployment.web3Wrapper.revertSnapshotAsync(snapshotId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,17 @@ class PRNGWrapper {
|
||||
return ONE.minus(ONE.minus(u).exponentiatedBy(ONE.dividedBy(beta))).exponentiatedBy(ONE.dividedBy(alpha));
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Pseudorandom version of `hexRandom()`. If no distribution is provided,
|
||||
* samples all byte values uniformly.
|
||||
*/
|
||||
public hex(bytesLength: number = 32, distribution: () => Numberish = this.rng): string {
|
||||
const buf = Buffer.from(_.times(bytesLength, () => this.integer(0, 255, distribution).toNumber())).toString(
|
||||
'hex',
|
||||
);
|
||||
return `0x${buf}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const Pseudorandom = new PRNGWrapper();
|
||||
|
@ -0,0 +1,725 @@
|
||||
import { ExchangeContract } from '@0x/contracts-exchange';
|
||||
import { blockchainTests, constants, expect, signingUtils, transactionHashUtils } from '@0x/contracts-test-utils';
|
||||
import { orderHashUtils } from '@0x/order-utils';
|
||||
import { Order, SignatureType, ZeroExTransaction } from '@0x/types';
|
||||
import { hexUtils, logUtils } from '@0x/utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import { AssertionResult } from '../framework/assertions/function_assertion';
|
||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
|
||||
import { DeploymentManager } from '../framework/deployment_manager';
|
||||
import { Simulation, SimulationEnvironment } from '../framework/simulation';
|
||||
import { Pseudorandom } from '../framework/utils/pseudorandom';
|
||||
import { TestSignatureValidationWalletContract } from '../wrappers';
|
||||
|
||||
// tslint:disable: max-classes-per-file no-non-null-assertion no-unnecessary-type-assertion
|
||||
const tests = process.env.FUZZ_TEST === 'exchange/signature_validation' ? blockchainTests : blockchainTests.skip;
|
||||
|
||||
tests('Exchange signature validation fuzz tests', env => {
|
||||
const ALL_SIGNATURE_TYPES = [
|
||||
SignatureType.Illegal,
|
||||
SignatureType.Invalid,
|
||||
SignatureType.EthSign,
|
||||
SignatureType.EIP712,
|
||||
SignatureType.Wallet,
|
||||
SignatureType.Validator,
|
||||
SignatureType.PreSigned,
|
||||
SignatureType.EIP1271Wallet,
|
||||
];
|
||||
const ALL_WORKING_SIGNATURE_TYPES = [
|
||||
SignatureType.EthSign,
|
||||
SignatureType.EIP712,
|
||||
SignatureType.Wallet,
|
||||
SignatureType.Validator,
|
||||
SignatureType.PreSigned,
|
||||
SignatureType.EIP1271Wallet,
|
||||
];
|
||||
const HASH_COMPATIBLE_SIGNATURE_TYPES = [
|
||||
SignatureType.EthSign,
|
||||
SignatureType.EIP712,
|
||||
SignatureType.Wallet,
|
||||
SignatureType.PreSigned,
|
||||
];
|
||||
const STATIC_SIGNATURE_TYPES = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.PreSigned];
|
||||
const ALWAYS_FAILING_SIGNATURE_TYPES = [SignatureType.Illegal, SignatureType.Invalid];
|
||||
const WALLET_SIGNATURE_TYPES = [SignatureType.Wallet, SignatureType.EIP1271Wallet];
|
||||
const STRICT_LENGTH_SIGNATURE_TYPES = [SignatureType.EthSign, SignatureType.EIP712];
|
||||
const CALLBACK_SIGNATURE_TYPES = [SignatureType.Wallet, SignatureType.EIP1271Wallet, SignatureType.Validator];
|
||||
|
||||
let walletContractAddress: string;
|
||||
let notWalletContractAddress: string;
|
||||
let deployment: DeploymentManager;
|
||||
let exchange: ExchangeContract;
|
||||
let accounts: string[];
|
||||
let privateKeys: { [address: string]: Buffer };
|
||||
let chainId: number;
|
||||
|
||||
interface SignatureTestParams {
|
||||
signatureType: SignatureType;
|
||||
signer: string;
|
||||
signature: string;
|
||||
hash: string;
|
||||
signerKey?: Buffer;
|
||||
validator?: string;
|
||||
payload?: string;
|
||||
order?: Order;
|
||||
transaction?: ZeroExTransaction;
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
chainId = await env.web3Wrapper.getChainIdAsync();
|
||||
accounts = await env.getAccountAddressesAsync();
|
||||
privateKeys = _.zipObject(accounts, accounts.map((a, i) => constants.TESTRPC_PRIVATE_KEYS[i]));
|
||||
deployment = await DeploymentManager.deployAsync(env, {
|
||||
numErc20TokensToDeploy: 0,
|
||||
numErc721TokensToDeploy: 0,
|
||||
numErc1155TokensToDeploy: 0,
|
||||
});
|
||||
exchange = deployment.exchange;
|
||||
walletContractAddress = (await TestSignatureValidationWalletContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestSignatureValidationWallet,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
)).address;
|
||||
// This just has to be a contract address that doesn't implement the
|
||||
// wallet spec.
|
||||
notWalletContractAddress = exchange.address;
|
||||
});
|
||||
|
||||
function randomPayload(): string {
|
||||
return Pseudorandom.hex(Pseudorandom.integer(0, 66).toNumber());
|
||||
}
|
||||
|
||||
async function presignHashAsync(signer: string, hash: string): Promise<void> {
|
||||
await exchange.preSign(hash).awaitTransactionSuccessAsync({ from: signer });
|
||||
}
|
||||
|
||||
async function approveValidatorAsync(signer: string, validator: string, approved: boolean = true): Promise<void> {
|
||||
await exchange
|
||||
.setSignatureValidatorApproval(validator, approved)
|
||||
.awaitTransactionSuccessAsync({ from: signer });
|
||||
}
|
||||
|
||||
function createSignature(params: {
|
||||
signatureType: SignatureType;
|
||||
hash?: string;
|
||||
signerKey?: Buffer;
|
||||
validator?: string;
|
||||
payload?: string;
|
||||
}): string {
|
||||
const payload = params.payload || constants.NULL_BYTES;
|
||||
const signatureByte = hexUtils.leftPad(params.signatureType, 1);
|
||||
switch (params.signatureType) {
|
||||
default:
|
||||
case SignatureType.Illegal:
|
||||
case SignatureType.Invalid:
|
||||
case SignatureType.PreSigned:
|
||||
return hexUtils.concat(payload, signatureByte);
|
||||
case SignatureType.EIP712:
|
||||
case SignatureType.EthSign:
|
||||
return hexUtils.concat(
|
||||
payload,
|
||||
ethUtil.bufferToHex(
|
||||
signingUtils.signMessage(
|
||||
ethUtil.toBuffer(params.hash),
|
||||
params.signerKey!,
|
||||
params.signatureType,
|
||||
),
|
||||
),
|
||||
);
|
||||
case SignatureType.Wallet:
|
||||
case SignatureType.EIP1271Wallet:
|
||||
return hexUtils.concat(payload, params.signatureType);
|
||||
case SignatureType.Validator:
|
||||
return hexUtils.concat(payload, params.validator!, params.signatureType);
|
||||
}
|
||||
}
|
||||
|
||||
async function mangleSignatureParamsAsync(params: SignatureTestParams): Promise<SignatureTestParams> {
|
||||
const mangled = { ...params };
|
||||
const MANGLE_MODES = [
|
||||
'TRUNCATE_SIGNATURE',
|
||||
'RETYPE_SIGNATURE',
|
||||
'RANDOM_HASH',
|
||||
'RANDOM_ORDER',
|
||||
'RANDOM_TRANSACTION',
|
||||
'RANDOM_SIGNER',
|
||||
];
|
||||
const invalidModes = [];
|
||||
if (!STRICT_LENGTH_SIGNATURE_TYPES.includes(mangled.signatureType)) {
|
||||
invalidModes.push('TRUNCATE_SIGNATURE');
|
||||
}
|
||||
if (CALLBACK_SIGNATURE_TYPES.includes(mangled.signatureType)) {
|
||||
invalidModes.push('RANDOM_HASH');
|
||||
}
|
||||
if (params.transaction === undefined) {
|
||||
invalidModes.push('RANDOM_TRANSACTION');
|
||||
}
|
||||
if (params.order === undefined) {
|
||||
invalidModes.push('RANDOM_ORDER');
|
||||
}
|
||||
if (params.order !== undefined || params.hash !== undefined) {
|
||||
invalidModes.push('RANDOM_HASH');
|
||||
}
|
||||
const mode = Pseudorandom.sample(_.without(MANGLE_MODES, ...invalidModes))!;
|
||||
switch (mode) {
|
||||
case 'TRUNCATE_SIGNATURE':
|
||||
while (hexUtils.slice(mangled.signature, -1) === hexUtils.leftPad(mangled.signatureType, 1)) {
|
||||
mangled.signature = hexUtils.slice(mangled.signature, 0, -1);
|
||||
}
|
||||
break;
|
||||
case 'RETYPE_SIGNATURE':
|
||||
mangled.signatureType = WALLET_SIGNATURE_TYPES.includes(mangled.signatureType)
|
||||
? Pseudorandom.sample(_.without(ALL_SIGNATURE_TYPES, ...WALLET_SIGNATURE_TYPES))!
|
||||
: Pseudorandom.sample(_.without(ALL_SIGNATURE_TYPES, mangled.signatureType))!;
|
||||
mangled.signature = hexUtils.concat(hexUtils.slice(mangled.signature, 0, -1), mangled.signatureType);
|
||||
break;
|
||||
case 'RANDOM_SIGNER':
|
||||
mangled.signer = Pseudorandom.hex(constants.ADDRESS_LENGTH);
|
||||
if (mangled.order) {
|
||||
mangled.order.makerAddress = mangled.signer;
|
||||
}
|
||||
if (mangled.transaction) {
|
||||
mangled.transaction.signerAddress = mangled.signer;
|
||||
}
|
||||
break;
|
||||
case 'RANDOM_HASH':
|
||||
mangled.hash = Pseudorandom.hex();
|
||||
break;
|
||||
case 'RANDOM_ORDER':
|
||||
mangled.order = randomOrder({
|
||||
exchangeAddress: mangled.order!.exchangeAddress,
|
||||
chainId: mangled.order!.chainId,
|
||||
});
|
||||
mangled.hash = await orderHashUtils.getOrderHashAsync(mangled.order);
|
||||
break;
|
||||
case 'RANDOM_TRANSACTION':
|
||||
mangled.transaction = randomTransaction({
|
||||
domain: mangled.transaction!.domain,
|
||||
});
|
||||
mangled.hash = await transactionHashUtils.getTransactionHashHex(mangled.transaction);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled mangle mode: ${mode}`);
|
||||
}
|
||||
return mangled;
|
||||
}
|
||||
|
||||
function createHashTestParams(fields: Partial<SignatureTestParams> = {}): SignatureTestParams {
|
||||
const signatureType =
|
||||
fields.signatureType === undefined
|
||||
? Pseudorandom.sample(HASH_COMPATIBLE_SIGNATURE_TYPES)!
|
||||
: fields.signatureType;
|
||||
const signer =
|
||||
fields.signer ||
|
||||
(WALLET_SIGNATURE_TYPES.includes(signatureType) ? walletContractAddress : Pseudorandom.sample(accounts)!);
|
||||
const validator =
|
||||
fields.validator || (signatureType === SignatureType.Validator ? walletContractAddress : undefined);
|
||||
const signerKey = fields.signerKey || privateKeys[signer];
|
||||
const hash = fields.hash || Pseudorandom.hex();
|
||||
const payload =
|
||||
fields.payload ||
|
||||
(STRICT_LENGTH_SIGNATURE_TYPES.includes(signatureType) ? constants.NULL_BYTES : randomPayload());
|
||||
const signature = createSignature({ signatureType, hash, signerKey, payload, validator });
|
||||
return {
|
||||
hash,
|
||||
payload,
|
||||
signature,
|
||||
signatureType,
|
||||
signer,
|
||||
signerKey,
|
||||
validator,
|
||||
};
|
||||
}
|
||||
|
||||
async function assertValidHashSignatureAsync(params: {
|
||||
hash: string;
|
||||
signer: string;
|
||||
signature: string;
|
||||
isValid: boolean;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
let result;
|
||||
try {
|
||||
result = await exchange.isValidHashSignature(params.hash, params.signer, params.signature).callAsync();
|
||||
} catch (err) {
|
||||
if (params.isValid) {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
expect(result).to.eq(!!params.isValid);
|
||||
} catch (err) {
|
||||
logUtils.warn(params);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function* validTestHashSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const { hash, signature, signatureType, signer } = createHashTestParams();
|
||||
yield (async () => {
|
||||
if (signatureType === SignatureType.PreSigned) {
|
||||
await presignHashAsync(signer, hash);
|
||||
}
|
||||
await assertValidHashSignatureAsync({
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
isValid: true,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestHashStaticSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const randomSignerKey = ethUtil.toBuffer(Pseudorandom.hex());
|
||||
const signer = Pseudorandom.sample([notWalletContractAddress, walletContractAddress, ...accounts])!;
|
||||
const { hash, signature } = createHashTestParams({
|
||||
signatureType: Pseudorandom.sample([...STATIC_SIGNATURE_TYPES, ...ALWAYS_FAILING_SIGNATURE_TYPES])!,
|
||||
signer,
|
||||
// Always sign with a random key.
|
||||
signerKey: randomSignerKey,
|
||||
});
|
||||
yield assertValidHashSignatureAsync({
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestHashWalletSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const signer = Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
|
||||
const { hash, signature } = createHashTestParams({
|
||||
signatureType: SignatureType.Wallet,
|
||||
signer,
|
||||
});
|
||||
yield assertValidHashSignatureAsync({
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestHashValidatorSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const isNotApproved = Pseudorandom.sample([true, false])!;
|
||||
const signer = Pseudorandom.sample([...accounts])!;
|
||||
const validator = isNotApproved
|
||||
? walletContractAddress
|
||||
: Pseudorandom.sample([
|
||||
// All validator signatures are invalid for the hash test, so passing a valid
|
||||
// wallet contract should still fail.
|
||||
walletContractAddress,
|
||||
notWalletContractAddress,
|
||||
...accounts,
|
||||
])!;
|
||||
const { hash, signature } = createHashTestParams({
|
||||
signatureType: SignatureType.Validator,
|
||||
validator,
|
||||
});
|
||||
yield (async () => {
|
||||
if (!isNotApproved) {
|
||||
await approveValidatorAsync(signer, validator);
|
||||
}
|
||||
await assertValidHashSignatureAsync({
|
||||
hash,
|
||||
signer,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestHashMangledSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const params = createHashTestParams({ signatureType: Pseudorandom.sample(ALL_SIGNATURE_TYPES)! });
|
||||
const mangled = await mangleSignatureParamsAsync(params);
|
||||
yield (async () => {
|
||||
await assertValidHashSignatureAsync({
|
||||
hash: mangled.hash,
|
||||
signer: mangled.signer,
|
||||
signature: mangled.signature,
|
||||
isValid: false,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
function randomOrder(fields: Partial<Order> = {}): Order {
|
||||
return {
|
||||
chainId,
|
||||
exchangeAddress: exchange.address,
|
||||
expirationTimeSeconds: Pseudorandom.integer(1, 2 ** 32),
|
||||
salt: Pseudorandom.integer(0, constants.MAX_UINT256),
|
||||
makerAssetData: Pseudorandom.hex(36),
|
||||
takerAssetData: Pseudorandom.hex(36),
|
||||
makerFeeAssetData: Pseudorandom.hex(36),
|
||||
takerFeeAssetData: Pseudorandom.hex(36),
|
||||
makerAssetAmount: Pseudorandom.integer(0, 100e18),
|
||||
takerAssetAmount: Pseudorandom.integer(0, 100e18),
|
||||
makerFee: Pseudorandom.integer(0, 100e18),
|
||||
takerFee: Pseudorandom.integer(0, 100e18),
|
||||
feeRecipientAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
|
||||
makerAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
|
||||
takerAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
|
||||
senderAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
async function createOrderTestParamsAsync(
|
||||
fields: Partial<SignatureTestParams> = {},
|
||||
): Promise<SignatureTestParams & { order: Order }> {
|
||||
const signatureType =
|
||||
fields.signatureType === undefined
|
||||
? Pseudorandom.sample(ALL_WORKING_SIGNATURE_TYPES)!
|
||||
: fields.signatureType;
|
||||
const signer =
|
||||
fields.signer ||
|
||||
(WALLET_SIGNATURE_TYPES.includes(signatureType) ? walletContractAddress : Pseudorandom.sample(accounts)!);
|
||||
const validator =
|
||||
fields.validator || (signatureType === SignatureType.Validator ? walletContractAddress : undefined);
|
||||
const signerKey = fields.signerKey || privateKeys[signer];
|
||||
const order = fields.order || randomOrder({ makerAddress: signer });
|
||||
const hash = fields.hash || (await orderHashUtils.getOrderHashAsync(order));
|
||||
const payload =
|
||||
fields.payload ||
|
||||
(STRICT_LENGTH_SIGNATURE_TYPES.includes(signatureType) ? constants.NULL_BYTES : randomPayload());
|
||||
const signature = createSignature({ signatureType, hash, signerKey, payload, validator });
|
||||
return {
|
||||
hash,
|
||||
order,
|
||||
payload,
|
||||
signature,
|
||||
signatureType,
|
||||
signer,
|
||||
signerKey,
|
||||
validator,
|
||||
};
|
||||
}
|
||||
|
||||
async function assertValidOrderSignatureAsync(params: {
|
||||
order: Order;
|
||||
signature: string;
|
||||
isValid: boolean;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
let result;
|
||||
try {
|
||||
result = await exchange.isValidOrderSignature(params.order, params.signature).callAsync();
|
||||
} catch (err) {
|
||||
if (params.isValid) {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
expect(result).to.eq(!!params.isValid);
|
||||
} catch (err) {
|
||||
logUtils.warn(params);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function* validTestOrderSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const { hash, order, signature, signatureType, signer, validator } = await createOrderTestParamsAsync();
|
||||
yield (async () => {
|
||||
if (signatureType === SignatureType.PreSigned) {
|
||||
await presignHashAsync(signer, hash);
|
||||
} else if (signatureType === SignatureType.Validator) {
|
||||
await approveValidatorAsync(signer, validator!);
|
||||
}
|
||||
await assertValidOrderSignatureAsync({
|
||||
order,
|
||||
signature,
|
||||
isValid: true,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestOrderStaticSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const randomSignerKey = ethUtil.toBuffer(Pseudorandom.hex());
|
||||
const signer = Pseudorandom.sample([notWalletContractAddress, walletContractAddress, ...accounts])!;
|
||||
const { order, signature } = await createOrderTestParamsAsync({
|
||||
signatureType: Pseudorandom.sample([...STATIC_SIGNATURE_TYPES, ...ALWAYS_FAILING_SIGNATURE_TYPES])!,
|
||||
signer,
|
||||
// Always sign with a random key.
|
||||
signerKey: randomSignerKey,
|
||||
});
|
||||
yield assertValidOrderSignatureAsync({
|
||||
order,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestOrderWalletSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const signer = Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
|
||||
const { order, signature } = await createOrderTestParamsAsync({
|
||||
signatureType: Pseudorandom.sample(WALLET_SIGNATURE_TYPES)!,
|
||||
signer,
|
||||
});
|
||||
yield assertValidOrderSignatureAsync({
|
||||
order,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestOrderValidatorSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const isNotApproved = Pseudorandom.sample([true, false])!;
|
||||
const signer = Pseudorandom.sample([...accounts])!;
|
||||
const validator = isNotApproved
|
||||
? walletContractAddress
|
||||
: Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
|
||||
const { order, signature } = await createOrderTestParamsAsync({
|
||||
signatureType: SignatureType.Validator,
|
||||
validator,
|
||||
});
|
||||
yield (async () => {
|
||||
if (!isNotApproved) {
|
||||
await approveValidatorAsync(signer, validator);
|
||||
}
|
||||
await assertValidOrderSignatureAsync({
|
||||
order,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestOrderMangledSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const params = await createOrderTestParamsAsync({
|
||||
signatureType: Pseudorandom.sample(ALL_SIGNATURE_TYPES)!,
|
||||
});
|
||||
const mangled = await mangleSignatureParamsAsync(params);
|
||||
yield (async () => {
|
||||
await assertValidOrderSignatureAsync({
|
||||
order: mangled.order!,
|
||||
signature: mangled.signature,
|
||||
isValid: false,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
function randomTransaction(fields: Partial<ZeroExTransaction> = {}): ZeroExTransaction {
|
||||
return {
|
||||
domain: {
|
||||
chainId,
|
||||
verifyingContract: exchange.address,
|
||||
name: '0x Protocol',
|
||||
version: '3.0.0',
|
||||
},
|
||||
gasPrice: Pseudorandom.integer(1e9, 100e9),
|
||||
expirationTimeSeconds: Pseudorandom.integer(1, 2 ** 32),
|
||||
salt: Pseudorandom.integer(0, constants.MAX_UINT256),
|
||||
signerAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
|
||||
data: Pseudorandom.hex(Pseudorandom.integer(4, 128).toNumber()),
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
async function createTransactionTestParamsAsync(
|
||||
fields: Partial<SignatureTestParams> = {},
|
||||
): Promise<SignatureTestParams & { transaction: ZeroExTransaction }> {
|
||||
const signatureType =
|
||||
fields.signatureType === undefined
|
||||
? Pseudorandom.sample(ALL_WORKING_SIGNATURE_TYPES)!
|
||||
: fields.signatureType;
|
||||
const signer =
|
||||
fields.signer ||
|
||||
(WALLET_SIGNATURE_TYPES.includes(signatureType) ? walletContractAddress : Pseudorandom.sample(accounts)!);
|
||||
const validator =
|
||||
fields.validator || (signatureType === SignatureType.Validator ? walletContractAddress : undefined);
|
||||
const signerKey = fields.signerKey || privateKeys[signer];
|
||||
const transaction = fields.transaction || randomTransaction({ signerAddress: signer });
|
||||
const hash = fields.hash || transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const payload =
|
||||
fields.payload ||
|
||||
(STRICT_LENGTH_SIGNATURE_TYPES.includes(signatureType) ? constants.NULL_BYTES : randomPayload());
|
||||
const signature = createSignature({ signatureType, hash, signerKey, payload, validator });
|
||||
return {
|
||||
hash,
|
||||
transaction,
|
||||
payload,
|
||||
signature,
|
||||
signatureType,
|
||||
signer,
|
||||
signerKey,
|
||||
validator,
|
||||
};
|
||||
}
|
||||
|
||||
async function assertValidTransactionSignatureAsync(params: {
|
||||
transaction: ZeroExTransaction;
|
||||
signature: string;
|
||||
isValid: boolean;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
let result;
|
||||
try {
|
||||
result = await exchange.isValidTransactionSignature(params.transaction, params.signature).callAsync();
|
||||
} catch (err) {
|
||||
if (params.isValid) {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
expect(result).to.eq(!!params.isValid);
|
||||
} catch (err) {
|
||||
logUtils.warn(params);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function* validTestTransactionSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const {
|
||||
hash,
|
||||
transaction,
|
||||
signature,
|
||||
signatureType,
|
||||
signer,
|
||||
validator,
|
||||
} = await createTransactionTestParamsAsync();
|
||||
yield (async () => {
|
||||
if (signatureType === SignatureType.PreSigned) {
|
||||
await presignHashAsync(signer, hash);
|
||||
} else if (signatureType === SignatureType.Validator) {
|
||||
await approveValidatorAsync(signer, validator!);
|
||||
}
|
||||
await assertValidTransactionSignatureAsync({
|
||||
transaction,
|
||||
signature,
|
||||
isValid: true,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestTransactionStaticSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const randomSignerKey = ethUtil.toBuffer(Pseudorandom.hex());
|
||||
const signer = Pseudorandom.sample([notWalletContractAddress, walletContractAddress, ...accounts])!;
|
||||
const { transaction, signature } = await createTransactionTestParamsAsync({
|
||||
signatureType: Pseudorandom.sample([...STATIC_SIGNATURE_TYPES, ...ALWAYS_FAILING_SIGNATURE_TYPES])!,
|
||||
signer,
|
||||
// Always sign with a random key.
|
||||
signerKey: randomSignerKey,
|
||||
});
|
||||
yield assertValidTransactionSignatureAsync({
|
||||
transaction,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestTransactionWalletSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const signer = Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
|
||||
const { transaction, signature } = await createTransactionTestParamsAsync({
|
||||
signatureType: Pseudorandom.sample(WALLET_SIGNATURE_TYPES)!,
|
||||
signer,
|
||||
});
|
||||
yield assertValidTransactionSignatureAsync({
|
||||
transaction,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestTransactionValidatorSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const isNotApproved = Pseudorandom.sample([true, false])!;
|
||||
const signer = Pseudorandom.sample([...accounts])!;
|
||||
const validator = isNotApproved
|
||||
? walletContractAddress
|
||||
: Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
|
||||
const { transaction, signature } = await createTransactionTestParamsAsync({
|
||||
signatureType: SignatureType.Validator,
|
||||
validator,
|
||||
});
|
||||
yield (async () => {
|
||||
if (!isNotApproved) {
|
||||
await approveValidatorAsync(signer, validator);
|
||||
}
|
||||
await assertValidTransactionSignatureAsync({
|
||||
transaction,
|
||||
signature,
|
||||
isValid: false,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
async function* invalidTestTransactionMangledSignature(): AsyncIterableIterator<void> {
|
||||
while (true) {
|
||||
const params = await createTransactionTestParamsAsync({
|
||||
signatureType: Pseudorandom.sample(ALL_SIGNATURE_TYPES)!,
|
||||
});
|
||||
const mangled = await mangleSignatureParamsAsync(params);
|
||||
yield (async () => {
|
||||
await assertValidTransactionSignatureAsync({
|
||||
transaction: mangled.transaction!,
|
||||
signature: mangled.signature,
|
||||
isValid: false,
|
||||
});
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
it('fuzz', async () => {
|
||||
const FUZZ_ACTIONS = [
|
||||
validTestHashSignature(),
|
||||
invalidTestHashStaticSignature(),
|
||||
invalidTestHashWalletSignature(),
|
||||
invalidTestHashValidatorSignature(),
|
||||
invalidTestHashMangledSignature(),
|
||||
validTestOrderSignature(),
|
||||
invalidTestOrderStaticSignature(),
|
||||
invalidTestOrderWalletSignature(),
|
||||
invalidTestOrderValidatorSignature(),
|
||||
invalidTestOrderMangledSignature(),
|
||||
validTestTransactionSignature(),
|
||||
invalidTestTransactionStaticSignature(),
|
||||
invalidTestTransactionWalletSignature(),
|
||||
invalidTestTransactionValidatorSignature(),
|
||||
invalidTestTransactionMangledSignature(),
|
||||
];
|
||||
const simulationEnvironment = new SimulationEnvironment(deployment, new BlockchainBalanceStore({}, {}), []);
|
||||
const simulation = new class extends Simulation {
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||
while (true) {
|
||||
const action = Pseudorandom.sample(FUZZ_ACTIONS)!;
|
||||
yield (await action!.next()).value;
|
||||
}
|
||||
}
|
||||
}(simulationEnvironment);
|
||||
simulation.resets = true;
|
||||
return simulation.fuzzAsync();
|
||||
});
|
||||
});
|
||||
// tslint:disable-next-line max-file-line-count
|
@ -8,6 +8,7 @@ export * from '../test/generated-wrappers/test_eth2_dai';
|
||||
export * from '../test/generated-wrappers/test_eth2_dai_bridge';
|
||||
export * from '../test/generated-wrappers/test_framework';
|
||||
export * from '../test/generated-wrappers/test_mainnet_aggregator_fills';
|
||||
export * from '../test/generated-wrappers/test_signature_validation_wallet';
|
||||
export * from '../test/generated-wrappers/test_uniswap_bridge';
|
||||
export * from '../test/generated-wrappers/test_uniswap_exchange';
|
||||
export * from '../test/generated-wrappers/test_uniswap_exchange_factory';
|
||||
|
@ -9,6 +9,7 @@
|
||||
"test/generated-artifacts/TestEth2DaiBridge.json",
|
||||
"test/generated-artifacts/TestFramework.json",
|
||||
"test/generated-artifacts/TestMainnetAggregatorFills.json",
|
||||
"test/generated-artifacts/TestSignatureValidationWallet.json",
|
||||
"test/generated-artifacts/TestUniswapBridge.json",
|
||||
"test/generated-artifacts/TestUniswapExchange.json",
|
||||
"test/generated-artifacts/TestUniswapExchangeFactory.json"
|
||||
|
Loading…
x
Reference in New Issue
Block a user