* WIP add combinatorial tests for internal Exchange functions * Change combinitorial testing strategy based on feedback * Check value of filled[orderHash] in updateFilledState tests * Add combinatorial tests for addFillResults * Add combinatorial tests for getPartialAmount * Implement generic `testWithReferenceFuncAsync` * Implement generic `testCombinatoriallyWithReferenceFuncAsync` * Add combinatorial tests for isRoundingError * Add combinatorial tests for calculateFillResults * Add support for Geth in internal contract tests * Fix contract artifacts * Change DECIMAL_PLACES to 78 and add a note. * Document new functions in utils * Optimize tests by only reseting state when needed * Rename/move some files * Print parameter names on failure in testWithReferenceFuncAsync * Add to changelog for utils package * Appease various linters * Rename some more things related to FillOrderCombinatorialUtils * Remove .only from test/exchange/internal.ts * Remove old test for isRoundingError and getPartialAmount * Appease linters again * Remove old todos * Fix typos, add comments, rename some things * Re-add some LibMath tests * Update contract internal tests to use new SafeMath revert reasons * Apply PR feedback from Amir * Apply PR feedback from Remco * Re-add networks to ZRXToken artifact * Remove duplicate Whitelist in compiler.json
187 lines
7.7 KiB
TypeScript
187 lines
7.7 KiB
TypeScript
import { RevertReason } from '@0xproject/types';
|
|
import { logUtils } from '@0xproject/utils';
|
|
import { NodeType } from '@0xproject/web3-wrapper';
|
|
import * as chai from 'chai';
|
|
import { TransactionReceipt, TransactionReceiptStatus, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
|
import * as _ from 'lodash';
|
|
|
|
import { web3Wrapper } from './web3_wrapper';
|
|
|
|
const expect = chai.expect;
|
|
|
|
let nodeType: NodeType | undefined;
|
|
|
|
// Represents the return value of a `sendTransaction` call. The Promise should
|
|
// resolve with either a transaction receipt or a transaction hash.
|
|
export type sendTransactionResult = Promise<TransactionReceipt | TransactionReceiptWithDecodedLogs | string>;
|
|
|
|
/**
|
|
* Returns ganacheError if the backing Ethereum node is Ganache and gethError
|
|
* if it is Geth.
|
|
* @param ganacheError the error to be returned if the backing node is Ganache.
|
|
* @param gethError the error to be returned if the backing node is Geth.
|
|
* @returns either the given ganacheError or gethError depending on the backing
|
|
* node.
|
|
*/
|
|
async function _getGanacheOrGethError(ganacheError: string, gethError: string): Promise<string> {
|
|
if (_.isUndefined(nodeType)) {
|
|
nodeType = await web3Wrapper.getNodeTypeAsync();
|
|
}
|
|
switch (nodeType) {
|
|
case NodeType.Ganache:
|
|
return ganacheError;
|
|
case NodeType.Geth:
|
|
return gethError;
|
|
default:
|
|
throw new Error(`Unknown node type: ${nodeType}`);
|
|
}
|
|
}
|
|
|
|
async function _getInsufficientFundsErrorMessageAsync(): Promise<string> {
|
|
return _getGanacheOrGethError("sender doesn't have enough funds", 'insufficient funds');
|
|
}
|
|
|
|
async function _getTransactionFailedErrorMessageAsync(): Promise<string> {
|
|
return _getGanacheOrGethError('revert', 'always failing transaction');
|
|
}
|
|
|
|
async function _getContractCallFailedErrorMessageAsync(): Promise<string> {
|
|
return _getGanacheOrGethError('revert', 'Contract call failed');
|
|
}
|
|
|
|
/**
|
|
* Returns the expected error message for an 'invalid opcode' resulting from a
|
|
* contract call. The exact error message depends on the backing Ethereum node.
|
|
*/
|
|
export async function getInvalidOpcodeErrorMessageForCallAsync(): Promise<string> {
|
|
return _getGanacheOrGethError('invalid opcode', 'Contract call failed');
|
|
}
|
|
|
|
/**
|
|
* Returns the expected error message for the given revert reason resulting from
|
|
* a sendTransaction call. The exact error message depends on the backing
|
|
* Ethereum node and whether it supports revert reasons.
|
|
* @param reason a specific revert reason.
|
|
* @returns the expected error message.
|
|
*/
|
|
export async function getRevertReasonOrErrorMessageForSendTransactionAsync(reason: RevertReason): Promise<string> {
|
|
return _getGanacheOrGethError(reason, 'always failing transaction');
|
|
}
|
|
|
|
/**
|
|
* Rejects if the given Promise does not reject with an error indicating
|
|
* insufficient funds.
|
|
* @param p a promise resulting from a contract call or sendTransaction call.
|
|
* @returns a new Promise which will reject if the conditions are not met and
|
|
* otherwise resolve with no value.
|
|
*/
|
|
export async function expectInsufficientFundsAsync<T>(p: Promise<T>): Promise<void> {
|
|
const errMessage = await _getInsufficientFundsErrorMessageAsync();
|
|
return expect(p).to.be.rejectedWith(errMessage);
|
|
}
|
|
|
|
/**
|
|
* Resolves if the the sendTransaction call fails with the given revert reason.
|
|
* However, since Geth does not support revert reasons for sendTransaction, this
|
|
* falls back to expectTransactionFailedWithoutReasonAsync if the backing
|
|
* Ethereum node is Geth.
|
|
* @param p a Promise resulting from a sendTransaction call
|
|
* @param reason a specific revert reason
|
|
* @returns a new Promise which will reject if the conditions are not met and
|
|
* otherwise resolve with no value.
|
|
*/
|
|
export async function expectTransactionFailedAsync(p: sendTransactionResult, reason: RevertReason): Promise<void> {
|
|
// HACK(albrow): This dummy `catch` should not be necessary, but if you
|
|
// remove it, there is an uncaught exception and the Node process will
|
|
// forcibly exit. It's possible this is a false positive in
|
|
// make-promises-safe.
|
|
p.catch(e => {
|
|
_.noop(e);
|
|
});
|
|
|
|
if (_.isUndefined(nodeType)) {
|
|
nodeType = await web3Wrapper.getNodeTypeAsync();
|
|
}
|
|
switch (nodeType) {
|
|
case NodeType.Ganache:
|
|
return expect(p).to.be.rejectedWith(reason);
|
|
case NodeType.Geth:
|
|
logUtils.warn(
|
|
'WARNING: Geth does not support revert reasons for sendTransaction. This test will pass if the transaction fails for any reason.',
|
|
);
|
|
return expectTransactionFailedWithoutReasonAsync(p);
|
|
default:
|
|
throw new Error(`Unknown node type: ${nodeType}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves if the transaction fails without a revert reason, or if the
|
|
* corresponding transactionReceipt has a status of 0 or '0', indicating
|
|
* failure.
|
|
* @param p a Promise resulting from a sendTransaction call
|
|
* @returns a new Promise which will reject if the conditions are not met and
|
|
* otherwise resolve with no value.
|
|
*/
|
|
export async function expectTransactionFailedWithoutReasonAsync(p: sendTransactionResult): Promise<void> {
|
|
return p
|
|
.then(async result => {
|
|
let txReceiptStatus: TransactionReceiptStatus;
|
|
if (_.isString(result)) {
|
|
// Result is a txHash. We need to make a web3 call to get the
|
|
// receipt, then get the status from the receipt.
|
|
const txReceipt = await web3Wrapper.awaitTransactionMinedAsync(result);
|
|
txReceiptStatus = txReceipt.status;
|
|
} else if ('status' in result) {
|
|
// Result is a transaction receipt, so we can get the status
|
|
// directly.
|
|
txReceiptStatus = result.status;
|
|
} else {
|
|
throw new Error('Unexpected result type: ' + typeof result);
|
|
}
|
|
expect(_.toString(txReceiptStatus)).to.equal(
|
|
'0',
|
|
'Expected transaction to fail but receipt had a non-zero status, indicating success',
|
|
);
|
|
})
|
|
.catch(async err => {
|
|
// If the promise rejects, we expect a specific error message,
|
|
// depending on the backing Ethereum node type.
|
|
const errMessage = await _getTransactionFailedErrorMessageAsync();
|
|
expect(err.message).to.include(errMessage);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resolves if the the contract call fails with the given revert reason.
|
|
* @param p a Promise resulting from a contract call
|
|
* @param reason a specific revert reason
|
|
* @returns a new Promise which will reject if the conditions are not met and
|
|
* otherwise resolve with no value.
|
|
*/
|
|
export async function expectContractCallFailed<T>(p: Promise<T>, reason: RevertReason): Promise<void> {
|
|
return expect(p).to.be.rejectedWith(reason);
|
|
}
|
|
|
|
/**
|
|
* Resolves if the contract call fails without a revert reason.
|
|
* @param p a Promise resulting from a contract call
|
|
* @returns a new Promise which will reject if the conditions are not met and
|
|
* otherwise resolve with no value.
|
|
*/
|
|
export async function expectContractCallFailedWithoutReasonAsync<T>(p: Promise<T>): Promise<void> {
|
|
const errMessage = await _getContractCallFailedErrorMessageAsync();
|
|
return expect(p).to.be.rejectedWith(errMessage);
|
|
}
|
|
|
|
/**
|
|
* Resolves if the contract creation/deployment fails without a revert reason.
|
|
* @param p a Promise resulting from a contract creation/deployment
|
|
* @returns a new Promise which will reject if the conditions are not met and
|
|
* otherwise resolve with no value.
|
|
*/
|
|
export async function expectContractCreationFailedWithoutReason<T>(p: Promise<T>): Promise<void> {
|
|
const errMessage = await _getTransactionFailedErrorMessageAsync();
|
|
return expect(p).to.be.rejectedWith(errMessage);
|
|
}
|