protocol/contracts/exchange/test/signature_validator.ts
F. Eugene Aumson f11d8a5bd8
@0x/order-utils refactors for v3: orderParsingUtils, signatureUtils, orderHashUtils, RevertErrors, transactionHashUtils (#2321)
* move orderParsingUtils from order-utils to connect

* Remove many functions from signatureUtils

Removed from the exported object, that is.  All of them are used in
other existing code, so they were all moved to be as local to their
usage as possible.

* remove orderHashUtils.isValidOrderHash()

* Move all *RevertErrors from order-utils...

...into their respective @0x/contracts- packages.

* Refactor @0x/order-utils' orderHashUtils away

- Move existing routines into @0x/contracts-test-utils

- Migrate non-contract-test callers to a newly-exposed getOrderHash()
method in DevUtils.

* Move all *RevertErrors from @0x/utils...

...into their respective @0x/contracts- packages.

* rm transactionHashUtils.isValidTransactionHash()

* DevUtils.sol: Fail yarn test if too big to deploy

* Refactor @0x/order-utils transactionHashUtils away

- Move existing routines into @0x/contracts-test-utils

- Migrate non-contract-test callers to a newly-exposed
getTransactionHash() method in DevUtils.

* Consolidate `Removed export...` CHANGELOG entries

* Rm EthBalanceChecker from devutils wrapper exports

* Stop importing from '.' or '.../src'

* fix builds

* fix prettier; dangling promise

* increase max bundle size
2019-11-14 17:14:24 -05:00

1142 lines
56 KiB
TypeScript

import { DevUtilsContract } from '@0x/contracts-dev-utils';
import {
blockchainTests,
constants,
expect,
hexConcat,
hexRandom,
LogDecoder,
OrderFactory,
orderHashUtils,
orderUtils,
randomAddress,
TransactionFactory,
transactionHashUtils,
} from '@0x/contracts-test-utils';
import { SignatureType, SignedOrder, SignedZeroExTransaction } from '@0x/types';
import { BigNumber, StringRevertError } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
import ExchangeRevertErrors = require('../src/revert_errors');
import { artifacts } from './artifacts';
import {
IEIP1271DataContract,
TestSignatureValidatorContract,
TestSignatureValidatorSignatureValidatorApprovalEventArgs,
TestValidatorWalletContract,
} from './wrappers';
import { ValidatorWalletAction } from './utils/constants';
// tslint:disable:no-unnecessary-type-assertion
blockchainTests.resets('MixinSignatureValidator', env => {
let chainId: number;
let signatureValidator: TestSignatureValidatorContract;
let validatorWallet: TestValidatorWalletContract;
let validatorWalletRevertReason: string;
let signerAddress: string;
let signerPrivateKey: Buffer;
let notSignerAddress: string;
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, env.provider, env.txDefaults);
const eip1271Data = new IEIP1271DataContract(constants.NULL_ADDRESS, env.provider, env.txDefaults);
before(async () => {
chainId = await env.getChainIdAsync();
const accounts = await env.getAccountAddressesAsync();
signerAddress = accounts[0];
notSignerAddress = accounts[1];
signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync(
artifacts.TestSignatureValidator,
env.provider,
env.txDefaults,
{},
new BigNumber(chainId),
);
validatorWallet = await TestValidatorWalletContract.deployFrom0xArtifactAsync(
artifacts.TestValidatorWallet,
env.provider,
env.txDefaults,
{},
signatureValidator.address,
);
validatorWalletRevertReason = await validatorWallet.REVERT_REASON().callAsync();
// Approve the validator for both signers.
await Promise.all(
[signerAddress, notSignerAddress].map(async (addr: string) => {
return signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, true)
.awaitTransactionSuccessAsync({ from: addr });
}),
);
signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(signerAddress)];
});
const SIGNATURE_LENGTH = 65;
const generateRandomSignature = (): string => hexRandom(SIGNATURE_LENGTH);
const hashBytes = (bytesHex: string): string => ethUtil.bufferToHex(ethUtil.sha3(ethUtil.toBuffer(bytesHex)));
const signDataHex = (dataHex: string, privateKey: Buffer): string => {
const ecSignature = ethUtil.ecsign(ethUtil.toBuffer(dataHex), privateKey);
return hexConcat(ecSignature.v, ecSignature.r, ecSignature.s);
};
type ValidateHashSignatureAsync = (
hashHex: string,
signerAddress: string,
signatureHex: string,
validatorAction?: ValidatorWalletAction,
validatorExpectedSignatureHex?: string,
) => Promise<any>;
const createHashSignatureTests = (
getCurrentHashHex: (signerAddress?: string) => string,
validateAsync: ValidateHashSignatureAsync,
) => {
it('should revert when signature is empty', async () => {
const hashHex = getCurrentHashHex();
const emptySignature = constants.NULL_BYTES;
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidLength,
hashHex,
signerAddress,
emptySignature,
);
const tx = validateAsync(hashHex, signerAddress, emptySignature);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when signature type is unsupported', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(SignatureType.NSignatureTypes);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.Unsupported,
hashHex,
signerAddress,
signatureHex,
);
const tx = validateAsync(hashHex, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when SignatureType=Illegal', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(SignatureType.Illegal);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.Illegal,
hashHex,
signerAddress,
signatureHex,
);
const tx = validateAsync(hashHex, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(SignatureType.Invalid);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
it('should revert when SignatureType=Invalid and signature length is non-zero', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat('0xdeadbeef', SignatureType.Invalid);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidLength,
hashHex,
signerAddress,
signatureHex,
);
const tx = validateAsync(hashHex, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should return true when SignatureType=EIP712 and signature is valid', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(signDataHex(hashHex, signerPrivateKey), SignatureType.EIP712);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=EIP712 and signature is invalid', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP712);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
it('should return true when SignatureType=EthSign and signature is valid', async () => {
// Create EthSign signature
const hashHex = getCurrentHashHex();
const orderHashWithEthSignPrefixHex = ethUtil.bufferToHex(
ethUtil.hashPersonalMessage(ethUtil.toBuffer(hashHex)),
);
const signatureHex = hexConcat(
signDataHex(orderHashWithEthSignPrefixHex, signerPrivateKey),
SignatureType.EthSign,
);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=EthSign and signature is invalid', async () => {
const hashHex = getCurrentHashHex();
// Create EthSign signature
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EthSign);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
it('should return true when SignatureType=Wallet and signature is valid', async () => {
const hashHex = getCurrentHashHex(validatorWallet.address);
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.Wallet);
const isValidSignature = await validateAsync(
hashHex,
validatorWallet.address,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=Wallet and signature is invalid', async () => {
const hashHex = getCurrentHashHex(validatorWallet.address);
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const notSignatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(notSignatureDataHex, SignatureType.Wallet);
// Validate signature
const isValidSignature = await validateAsync(
hashHex,
validatorWallet.address,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should revert when validator attempts to update state and SignatureType=Wallet', async () => {
const hashHex = getCurrentHashHex(validatorWallet.address);
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.Wallet);
const expectedError = new ExchangeRevertErrors.SignatureWalletError(
hashHex,
validatorWallet.address,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.UpdateState);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when signer is an EOA and SignatureType=Wallet', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(SignatureType.Wallet);
const expectedError = new ExchangeRevertErrors.SignatureWalletError(
hashHex,
signerAddress,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(hashHex, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should return false when validator returns `true` and SignatureType=Wallet', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(SignatureType.Wallet);
const isValidSignature = await validateAsync(
hashHex,
validatorWallet.address,
signatureHex,
ValidatorWalletAction.ReturnTrue,
);
expect(isValidSignature).to.be.false();
});
it('should revert when validator returns nothing and SignatureType=Wallet', async () => {
const hashHex = getCurrentHashHex(validatorWallet.address);
const signatureHex = hexConcat(SignatureType.Wallet);
const expectedError = new ExchangeRevertErrors.SignatureWalletError(
hashHex,
validatorWallet.address,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(
hashHex,
validatorWallet.address,
signatureHex,
ValidatorWalletAction.ReturnNothing,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator reverts and SignatureType=Wallet', async () => {
const hashHex = getCurrentHashHex(validatorWallet.address);
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.Wallet);
const expectedError = new ExchangeRevertErrors.SignatureWalletError(
hashHex,
validatorWallet.address,
signatureHex,
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should return true when SignatureType=Presigned and signer has presigned hash', async () => {
const hashHex = getCurrentHashHex();
// Presign the hash
await signatureValidator.preSign(hashHex).awaitTransactionSuccessAsync({ from: signerAddress });
// Validate presigned signature
const signatureHex = hexConcat(SignatureType.PreSigned);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => {
const hashHex = getCurrentHashHex();
const signatureHex = hexConcat(SignatureType.PreSigned);
const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
};
describe('isValidHashSignature', () => {
let hashHex: string;
beforeEach(async () => {
hashHex = orderUtils.generatePseudoRandomOrderHash();
});
const validateAsync = async (
_hashHex: string,
_signerAddress: string,
signatureHex: string,
validatorAction?: ValidatorWalletAction,
validatorExpectedSignatureHex?: string,
) => {
const expectedSignatureHashHex =
validatorExpectedSignatureHex === undefined
? constants.NULL_BYTES
: hashBytes(validatorExpectedSignatureHex);
if (validatorAction !== undefined) {
await validatorWallet
.prepare(_hashHex, validatorAction, expectedSignatureHashHex)
.awaitTransactionSuccessAsync();
}
return signatureValidator.isValidHashSignature(_hashHex, _signerAddress, signatureHex).callAsync();
};
it('should revert when signerAddress == 0', async () => {
const signatureHex = hexConcat(SignatureType.EIP712);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidSigner,
hashHex,
constants.NULL_ADDRESS,
signatureHex,
);
const tx = validateAsync(hashHex, constants.NULL_ADDRESS, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when SignatureType=Validator', async () => {
const signatureHex = hexConcat(SignatureType.Validator);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InappropriateSignatureType,
hashHex,
signerAddress,
signatureHex,
);
const tx = validateAsync(hashHex, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when SignatureType=EIP1271Wallet', async () => {
const signatureHex = hexConcat(SignatureType.EIP1271Wallet);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InappropriateSignatureType,
hashHex,
signerAddress,
signatureHex,
);
const tx = validateAsync(hashHex, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => {
// messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++'));
const signer = '0xc28b145f10f0bcf0fc000e778615f8fd73490bad';
const v = ethUtil.toBuffer('0x1c');
const r = ethUtil.toBuffer('0x7b888b596ccf87f0bacab0dcb483124973f7420f169b4824d7a12534ac1e9832');
const s = ethUtil.toBuffer('0x0c8e14f7edc01459e13965f1da56e0c23ed11e2cca932571eee1292178f90424');
const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`);
const signature = Buffer.concat([v, r, s, trezorSignatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await signatureValidator
.isValidHashSignature(messageHash, signer, signatureHex)
.callAsync();
expect(isValidSignature).to.be.true();
});
it('should return true when message was signed by a Trezor Model T (firmware version 2.0.7)', async () => {
// messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++'));
const signer = '0x98ce6d9345e8ffa7d99ee0822272fae9d2c0e895';
const v = ethUtil.toBuffer('0x1c');
const r = ethUtil.toBuffer('0x423b71062c327f0ec4fe199b8da0f34185e59b4c1cb4cc23df86cac4a601fb3f');
const s = ethUtil.toBuffer('0x53810d6591b5348b7ee08ee812c874b0fdfb942c9849d59512c90e295221091f');
const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`);
const signature = Buffer.concat([v, r, s, trezorSignatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await signatureValidator
.isValidHashSignature(messageHash, signer, signatureHex)
.callAsync();
expect(isValidSignature).to.be.true();
});
createHashSignatureTests((_signerAddress?: string) => hashHex, validateAsync);
});
describe('isValidOrderSignature', () => {
let orderFactory: OrderFactory;
let signedOrder: SignedOrder;
before(async () => {
const makerAddress = signerAddress;
const defaultOrderParams = {
...constants.STATIC_ORDER_PARAMS,
makerAddress,
feeRecipientAddress: randomAddress(),
makerAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(),
takerAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(),
makerFeeAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(),
takerFeeAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(),
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
exchangeAddress: signatureValidator.address,
chainId,
};
orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams);
});
beforeEach(async () => {
signedOrder = await orderFactory.newSignedOrderAsync();
});
const validateAsync = async (
order: SignedOrder,
signatureHex: string,
validatorAction?: ValidatorWalletAction,
validatorExpectedSignatureHex?: string,
) => {
const orderHashHex = orderHashUtils.getOrderHashHex(order);
const expectedSignatureHashHex =
validatorExpectedSignatureHex === undefined
? constants.NULL_BYTES
: hashBytes(validatorExpectedSignatureHex);
if (validatorAction !== undefined) {
await validatorWallet
.prepare(orderHashHex, validatorAction, expectedSignatureHashHex)
.awaitTransactionSuccessAsync();
}
return signatureValidator.isValidOrderSignature(order, signatureHex).callAsync();
};
it('should revert when signerAddress == 0', async () => {
const signatureHex = hexConcat(SignatureType.EIP712);
const nullMakerOrder = {
...signedOrder,
makerAddress: constants.NULL_ADDRESS,
};
const orderHashHex = orderHashUtils.getOrderHashHex(nullMakerOrder);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidSigner,
orderHashHex,
constants.NULL_ADDRESS,
signatureHex,
);
const tx = signatureValidator.isValidOrderSignature(nullMakerOrder, signatureHex).callAsync();
return expect(tx).to.revertWith(expectedError);
});
it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const isValidSignature = await validateAsync(
signedOrder,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const notSignatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(notSignatureDataHex, validatorWallet.address, SignatureType.Validator);
const isValidSignature = await validateAsync(
signedOrder,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should return false when validator returns `true` and SignatureType=Validator', async () => {
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const isValidSignature = await validateAsync(
signedOrder,
signatureHex,
ValidatorWalletAction.ReturnTrue,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should revert when validator returns nothing and SignatureType=Validator', async () => {
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.ReturnNothing, signatureDataHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator attempts to update state and SignatureType=Validator', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.UpdateState);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator reverts and SignatureType=Validator', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when SignatureType=Validator and signature is shorter than 21 bytes', async () => {
// Set approval of signature validator to false
await signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, false)
.awaitTransactionSuccessAsync({ from: signedOrder.makerAddress });
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(SignatureType.Validator);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidLength,
orderHashHex,
signedOrder.makerAddress,
signatureHex,
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.MatchSignatureHash);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when SignatureType=Validator, signature is valid and validator is not approved', async () => {
// Set approval of signature validator to false
await signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, false)
.awaitTransactionSuccessAsync({ from: signedOrder.makerAddress });
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const expectedError = new ExchangeRevertErrors.SignatureValidatorNotApprovedError(
signedOrder.makerAddress,
validatorWallet.address,
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should return true when SignatureType=EIP1271Wallet and signature is valid', async () => {
signedOrder.makerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet);
// Validate signature
const isValidSignature = await validateAsync(
signedOrder,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=EIP1271Wallet and signature is invalid', async () => {
signedOrder.makerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const notSignatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(notSignatureDataHex, SignatureType.EIP1271Wallet);
// Validate signature
const isValidSignature = await validateAsync(
signedOrder,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should return false when validator returns `true` and SignatureType=EIP1271Wallet', async () => {
signedOrder.makerAddress = validatorWallet.address;
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet);
// Validate signature
const isValidSignature = await validateAsync(
signedOrder,
signatureHex,
ValidatorWalletAction.ReturnTrue,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should revert when validator returns nothing and SignatureType=EIP1271Wallet', async () => {
signedOrder.makerAddress = validatorWallet.address;
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.ReturnNothing, signatureDataHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator attempts to update state and SignatureType=EIP1271Wallet', async () => {
signedOrder.makerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP1271Wallet);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.UpdateState);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator reverts and SignatureType=EIP1271Wallet', async () => {
signedOrder.makerAddress = validatorWallet.address;
const signatureHex = hexConcat(SignatureType.EIP1271Wallet);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when signer is an EOA and SignatureType=EIP1271Wallet', async () => {
const signatureHex = hexConcat(SignatureType.EIP1271Wallet);
signedOrder.makerAddress = notSignerAddress;
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
notSignerAddress,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = signatureValidator.isValidOrderSignature(signedOrder, signatureHex).callAsync();
return expect(tx).to.revertWith(expectedError);
});
it('should revert when signer is an EOA and SignatureType=Validator', async () => {
const signatureHex = hexConcat(notSignerAddress, SignatureType.Validator);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
notSignerAddress,
data,
signatureHex,
constants.NULL_BYTES,
);
// Register an EOA as a validator.
await signatureValidator
.setSignatureValidatorApproval(notSignerAddress, true)
.awaitTransactionSuccessAsync({ from: signerAddress });
const tx = signatureValidator.isValidOrderSignature(signedOrder, signatureHex).callAsync();
return expect(tx).to.revertWith(expectedError);
});
// Run hash-only signature type tests as well.
const validateOrderHashAsync = async (
hashHex: string,
_signerAddress: string,
signatureHex: string,
validatorAction?: ValidatorWalletAction,
validatorExpectedSignatureHex?: string,
): Promise<any> => {
signedOrder.makerAddress = _signerAddress;
return validateAsync(signedOrder, signatureHex, validatorAction, validatorExpectedSignatureHex);
};
createHashSignatureTests((_signerAddress?: string) => {
signedOrder.makerAddress = _signerAddress === undefined ? signerAddress : _signerAddress;
return orderHashUtils.getOrderHashHex(signedOrder);
}, validateOrderHashAsync);
});
describe('isValidTransactionSignature', () => {
let transactionFactory: TransactionFactory;
let signedTransaction: SignedZeroExTransaction;
const TRANSACTION_DATA_LENGTH = 100;
before(async () => {
transactionFactory = new TransactionFactory(signerPrivateKey, signatureValidator.address, chainId);
});
beforeEach(async () => {
// We don't actually do anything with the transaction so we can just
// fill it with random data.
signedTransaction = await transactionFactory.newSignedTransactionAsync({
data: hexRandom(TRANSACTION_DATA_LENGTH),
});
});
const validateAsync = async (
transaction: SignedZeroExTransaction,
signatureHex: string,
validatorAction?: ValidatorWalletAction,
validatorExpectedSignatureHex?: string,
) => {
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
const expectedSignatureHashHex =
validatorExpectedSignatureHex === undefined
? constants.NULL_BYTES
: hashBytes(validatorExpectedSignatureHex);
if (validatorAction !== undefined) {
await validatorWallet
.prepare(transactionHashHex, validatorAction, expectedSignatureHashHex)
.awaitTransactionSuccessAsync();
}
return signatureValidator.isValidTransactionSignature(transaction, signatureHex).callAsync();
};
it('should revert when signerAddress == 0', async () => {
const signatureHex = hexConcat(SignatureType.EIP712);
const nullSignerTransaction = {
...signedTransaction,
signerAddress: constants.NULL_ADDRESS,
};
const transactionHashHex = transactionHashUtils.getTransactionHashHex(nullSignerTransaction);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidSigner,
transactionHashHex,
constants.NULL_ADDRESS,
signatureHex,
);
const tx = signatureValidator.isValidTransactionSignature(nullSignerTransaction, signatureHex).callAsync();
return expect(tx).to.revertWith(expectedError);
});
it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const isValidSignature = await validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const notSignatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(notSignatureDataHex, validatorWallet.address, SignatureType.Validator);
const isValidSignature = await validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should return false when validator returns `true` and SignatureType=Validator', async () => {
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const isValidSignature = await validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.ReturnTrue,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should revert when SignatureType=Validator and signature is shorter than 21 bytes', async () => {
// Set approval of signature validator to false
await signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, false)
.awaitTransactionSuccessAsync({ from: signedTransaction.signerAddress });
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(SignatureType.Validator);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InvalidLength,
transactionHashHex,
signedTransaction.signerAddress,
signatureHex,
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.MatchSignatureHash);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator returns nothing and SignatureType=Validator', async () => {
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.ReturnNothing,
signatureDataHex,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator attempts to update state and SignatureType=Validator', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.UpdateState);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator reverts and SignatureType=Validator', async () => {
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when SignatureType=Validator, signature is valid and validator is not approved', async () => {
// Set approval of signature validator to false
await signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, false)
.awaitTransactionSuccessAsync({ from: signedTransaction.signerAddress });
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator);
const expectedError = new ExchangeRevertErrors.SignatureValidatorNotApprovedError(
signedTransaction.signerAddress,
validatorWallet.address,
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should return true when SignatureType=EIP1271Wallet and signature is valid', async () => {
signedTransaction.signerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet);
// Validate signature
const isValidSignature = await validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=EIP1271Wallet and signature is invalid', async () => {
signedTransaction.signerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureDataHex = generateRandomSignature();
const notSignatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(notSignatureDataHex, SignatureType.EIP1271Wallet);
// Validate signature
const isValidSignature = await validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.MatchSignatureHash,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should return false when validator returns `true` and SignatureType=EIP1271Wallet', async () => {
signedTransaction.signerAddress = validatorWallet.address;
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet);
// Validate signature
const isValidSignature = await validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.ReturnTrue,
signatureDataHex,
);
expect(isValidSignature).to.be.false();
});
it('should revert when validator returns nothing and SignatureType=EIP1271Wallet', async () => {
signedTransaction.signerAddress = validatorWallet.address;
const signatureDataHex = generateRandomSignature();
const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(
signedTransaction,
signatureHex,
ValidatorWalletAction.ReturnNothing,
signatureDataHex,
);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator attempts to update state and SignatureType=EIP1271Wallet', async () => {
signedTransaction.signerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP1271Wallet);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.UpdateState);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when validator reverts and SignatureType=EIP1271Wallet', async () => {
signedTransaction.signerAddress = validatorWallet.address;
// Doesn't have to contain a real signature since our wallet contract
// just does a hash comparison.
const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP1271Wallet);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
validatorWallet.address,
data,
signatureHex,
new StringRevertError(validatorWalletRevertReason).encode(),
);
const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when signer is an EOA and SignatureType=EIP1271Wallet', async () => {
const signatureHex = hexConcat(SignatureType.EIP1271Wallet);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
signedTransaction.signerAddress,
data,
signatureHex,
constants.NULL_BYTES,
);
const tx = signatureValidator.isValidTransactionSignature(signedTransaction, signatureHex).callAsync();
return expect(tx).to.revertWith(expectedError);
});
it('should revert when signer is an EOA and SignatureType=Validator', async () => {
const signatureHex = hexConcat(notSignerAddress, SignatureType.Validator);
const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction);
const data = eip1271Data
.ZeroExTransactionWithHash(signedTransaction, transactionHashHex)
.getABIEncodedTransactionData();
const expectedError = new ExchangeRevertErrors.EIP1271SignatureError(
notSignerAddress,
data,
signatureHex,
constants.NULL_BYTES,
);
// Register an EOA as a validator.
await signatureValidator
.setSignatureValidatorApproval(notSignerAddress, true)
.awaitTransactionSuccessAsync({ from: signerAddress });
const tx = signatureValidator.isValidTransactionSignature(signedTransaction, signatureHex).callAsync();
return expect(tx).to.revertWith(expectedError);
});
// Run hash-only signature type tests as well.
const validateOrderHashAsync = async (
hashHex: string,
_signerAddress: string,
signatureHex: string,
validatorAction?: ValidatorWalletAction,
validatorExpectedSignatureHex?: string,
): Promise<any> => {
signedTransaction.signerAddress = _signerAddress;
return validateAsync(signedTransaction, signatureHex, validatorAction, validatorExpectedSignatureHex);
};
createHashSignatureTests((_signerAddress?: string) => {
signedTransaction.signerAddress = _signerAddress === undefined ? signerAddress : _signerAddress;
return transactionHashUtils.getTransactionHashHex(signedTransaction);
}, validateOrderHashAsync);
});
describe('setSignatureValidatorApproval', () => {
let signatureValidatorLogDecoder: LogDecoder;
before(async () => {
signatureValidatorLogDecoder = new LogDecoder(env.web3Wrapper, artifacts);
});
it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => {
const approval = true;
const res = await signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, approval)
.awaitTransactionSuccessAsync({
from: signerAddress,
});
expect(res.logs.length).to.equal(1);
const log = signatureValidatorLogDecoder.decodeLogOrThrow(res.logs[0]) as LogWithDecodedArgs<
TestSignatureValidatorSignatureValidatorApprovalEventArgs
>;
const logArgs = log.args;
expect(logArgs.signerAddress).to.equal(signerAddress);
expect(logArgs.validatorAddress).to.equal(validatorWallet.address);
expect(logArgs.isApproved).to.equal(approval);
});
it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => {
const approval = false;
const res = await signatureValidator
.setSignatureValidatorApproval(validatorWallet.address, approval)
.awaitTransactionSuccessAsync({
from: signerAddress,
});
expect(res.logs.length).to.equal(1);
const log = signatureValidatorLogDecoder.decodeLogOrThrow(res.logs[0]) as LogWithDecodedArgs<
TestSignatureValidatorSignatureValidatorApprovalEventArgs
>;
const logArgs = log.args;
expect(logArgs.signerAddress).to.equal(signerAddress);
expect(logArgs.validatorAddress).to.equal(validatorWallet.address);
expect(logArgs.isApproved).to.equal(approval);
});
});
});
// tslint:disable:max-file-line-count
// tslint:enable:no-unnecessary-type-assertion