@0x/contracts-exchange-libs: Correct internal variable naming in src/index.ts.

`@0x/contracts-utils`: Correct internal variable naming in `src/index.ts`.
`@0x/contracts-exchange`: Remove functions from `TestExchangeInternals.sol` that are now in other packages.
`@0x/contracts-exchange`: Remove `TestExchangeMath.sol`. Exchange math functions are now tested in `@0x/contracts-exchange-libs`.
`@0x/contracts-exchange`: Move `ReferenceFunctions` to default package export.
`@0x/contracts-exchange`: Update `match_order.ts` tests to use reference math functions instead of `TestExchangeMath`.
`@0x/contracts-exchange`: Remove `_updateFilledState()` combinatorial tests in favor of normal unit testing. Combinatorial testing was overkill.
`@0x/contracts-exchange`: Update/refactor `calculateFillResults()` combinatorial tests to use the reference functions and hide them behind `TEST_ALL`.
This commit is contained in:
Lawrence Forman
2019-08-01 18:17:46 -04:00
parent 264b1d69d9
commit 51391b7f0e
16 changed files with 193 additions and 663 deletions

View File

@@ -40,7 +40,6 @@
"test/ReentrantERC20Token.sol",
"test/TestAssetProxyDispatcher.sol",
"test/TestExchangeInternals.sol",
"test/TestExchangeMath.sol",
"test/TestLibExchangeRichErrorDecoder.sol",
"test/TestSignatureValidator.sol",
"test/TestValidatorWallet.sol"

View File

@@ -31,26 +31,6 @@ contract TestExchangeInternals is
Exchange(chainId)
{}
/// @dev Adds properties of both FillResults instances.
/// Modifies the first FillResults instance specified.
/// Note that this function has been modified from the original
// internal version to return the FillResults.
/// @param totalFillResults Fill results instance that will be added onto.
/// @param singleFillResults Fill results instance that will be added to totalFillResults.
/// @return newTotalFillResults The result of adding singleFillResults to totalFilResults.
function addFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults)
public
pure
returns (FillResults memory)
{
_addFillResults(totalFillResults, singleFillResults);
return totalFillResults;
}
/// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled.
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function calculateFillResults(
Order memory order,
uint256 takerAssetFilledAmount
@@ -62,12 +42,9 @@ contract TestExchangeInternals is
return _calculateFillResults(order, takerAssetFilledAmount);
}
/// @dev Updates state with results of a fill order.
/// @param order that was filled.
/// @param takerAddress Address of taker who filled the order.
/// @param orderTakerAssetFilledAmount Amount of order already filled.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function updateFilledState(
/// @dev Call `_updateFilledState()` but first set `filled[order]` to
/// `orderTakerAssetFilledAmount`.
function testUpdateFilledState(
Order memory order,
address takerAddress,
bytes32 orderHash,
@@ -76,6 +53,7 @@ contract TestExchangeInternals is
)
public
{
filled[getOrderHash(order)] = orderTakerAssetFilledAmount;
_updateFilledState(
order,
takerAddress,

View File

@@ -1,131 +0,0 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
contract TestExchangeMath is
LibMath
{
/// @dev Calculates partial value given a numerator and denominator.
/// Reverts if rounding error is >= 0.1%
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmountFloor(
uint256 numerator,
uint256 denominator,
uint256 target
)
public
pure
returns (uint256 partialAmount)
{
return _safeGetPartialAmountFloor(numerator, denominator, target);
}
/// @dev Calculates partial value given a numerator and denominator.
/// Reverts if rounding error is >= 0.1%
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmountCeil(
uint256 numerator,
uint256 denominator,
uint256 target
)
public
pure
returns (uint256 partialAmount)
{
return _safeGetPartialAmountCeil(numerator, denominator, target);
}
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function getPartialAmountFloor(
uint256 numerator,
uint256 denominator,
uint256 target
)
public
pure
returns (uint256 partialAmount)
{
return _getPartialAmountFloor(numerator, denominator, target);
}
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function getPartialAmountCeil(
uint256 numerator,
uint256 denominator,
uint256 target
)
public
pure
returns (uint256 partialAmount)
{
return _getPartialAmountCeil(numerator, denominator, target);
}
/// @dev Checks if rounding error >= 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
function isRoundingErrorFloor(
uint256 numerator,
uint256 denominator,
uint256 target
)
public
pure
returns (bool isError)
{
return _isRoundingErrorFloor(numerator, denominator, target);
}
/// @dev Checks if rounding error >= 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
function isRoundingErrorCeil(
uint256 numerator,
uint256 denominator,
uint256 target
)
public
pure
returns (bool isError)
{
return _isRoundingErrorCeil(numerator, denominator, target);
}
}

View File

@@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|IWallet|IWrapperFunctions|IsolatedExchange|ReentrantERC20Token|TestAssetProxyDispatcher|TestExchangeInternals|TestExchangeMath|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|Whitelist).json",
"abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|IWallet|IWrapperFunctions|IsolatedExchange|ReentrantERC20Token|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|Whitelist).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {

View File

@@ -20,7 +20,6 @@ import * as IsolatedExchange from '../generated-artifacts/IsolatedExchange.json'
import * as ReentrantERC20Token from '../generated-artifacts/ReentrantERC20Token.json';
import * as TestAssetProxyDispatcher from '../generated-artifacts/TestAssetProxyDispatcher.json';
import * as TestExchangeInternals from '../generated-artifacts/TestExchangeInternals.json';
import * as TestExchangeMath from '../generated-artifacts/TestExchangeMath.json';
import * as TestLibExchangeRichErrorDecoder from '../generated-artifacts/TestLibExchangeRichErrorDecoder.json';
import * as TestSignatureValidator from '../generated-artifacts/TestSignatureValidator.json';
import * as TestValidatorWallet from '../generated-artifacts/TestValidatorWallet.json';
@@ -38,11 +37,10 @@ export const artifacts = {
ITransactions: ITransactions as ContractArtifact,
IWallet: IWallet as ContractArtifact,
IWrapperFunctions: IWrapperFunctions as ContractArtifact,
IsolatedExchange: IsolatedExchange as ContractArtifact,
ReentrantERC20Token: ReentrantERC20Token as ContractArtifact,
TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact,
TestExchangeInternals: TestExchangeInternals as ContractArtifact,
TestExchangeMath: TestExchangeMath as ContractArtifact,
IsolatedExchange: IsolatedExchange as ContractArtifact,
TestLibExchangeRichErrorDecoder: TestLibExchangeRichErrorDecoder as ContractArtifact,
TestSignatureValidator: TestSignatureValidator as ContractArtifact,
TestValidatorWallet: TestValidatorWallet as ContractArtifact,

View File

@@ -1,3 +1,6 @@
export * from './artifacts';
export * from './wrappers';
export * from '../test/utils';
import * as ReferenceFunctionsToExport from './reference_functions';
export import ReferenceFunctions = ReferenceFunctionsToExport;

View File

@@ -18,7 +18,6 @@ export * from '../generated-wrappers/isolated_exchange';
export * from '../generated-wrappers/reentrant_erc20_token';
export * from '../generated-wrappers/test_asset_proxy_dispatcher';
export * from '../generated-wrappers/test_exchange_internals';
export * from '../generated-wrappers/test_exchange_math';
export * from '../generated-wrappers/test_lib_exchange_rich_error_decoder';
export * from '../generated-wrappers/test_signature_validator';
export * from '../generated-wrappers/test_validator_wallet';

View File

@@ -1,426 +1,60 @@
import {
blockchainTests,
bytes32Values,
constants,
describe,
expect,
testCombinatoriallyWithReferenceFuncAsync,
hexRandom,
LogDecoder,
testCombinatoriallyWithReferenceFunc,
uint256Values,
} from '@0x/contracts-test-utils';
import { LibMathRevertErrors } from '@0x/order-utils';
import { FillResults, Order, RevertReason, SignedOrder } from '@0x/types';
import { BigNumber, providerUtils, SafeMathRevertErrors } from '@0x/utils';
import { FillResults, OrderWithoutDomain as Order } from '@0x/types';
import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import * as _ from 'lodash';
import { artifacts, TestExchangeInternalsContract, TestExchangeMathContract } from '../src';
import {
artifacts,
ReferenceFunctions,
TestExchangeInternalsContract,
TestExchangeInternalsFillEventArgs,
} from '../src';
const { MAX_UINT256 } = constants;
const emptyOrder: Order = {
senderAddress: constants.NULL_ADDRESS,
makerAddress: constants.NULL_ADDRESS,
takerAddress: constants.NULL_ADDRESS,
makerFee: new BigNumber(0),
takerFee: new BigNumber(0),
makerAssetAmount: new BigNumber(0),
takerAssetAmount: new BigNumber(0),
makerAssetData: '0x',
takerAssetData: '0x',
makerFeeAssetData: '0x',
takerFeeAssetData: '0x',
salt: new BigNumber(0),
feeRecipientAddress: constants.NULL_ADDRESS,
expirationTimeSeconds: new BigNumber(0),
domain: {
verifyingContractAddress: constants.NULL_ADDRESS,
chainId: 0, // To be filled in later.
},
};
const emptySignedOrder: SignedOrder = {
...emptyOrder,
signature: '',
};
const safeMathErrorForCall = new SafeMathRevertErrors.SafeMathError();
// TODO(dorothy-zbornak): Move this to `exchange-libs` and `utils`.
blockchainTests.resets('Exchange math internal functions', env => {
let chainId: number;
let testExchange: TestExchangeMathContract;
let divisionByZeroErrorForCall: Error | undefined;
let roundingErrorForCall: Error | undefined;
before(async () => {
chainId = await env.getChainIdAsync();
emptyOrder.domain.chainId = chainId;
emptySignedOrder.domain.chainId = chainId;
testExchange = await TestExchangeMathContract.deployFrom0xArtifactAsync(
artifacts.TestExchangeMath,
env.provider,
env.txDefaults,
);
divisionByZeroErrorForCall = new Error(RevertReason.DivisionByZero);
roundingErrorForCall = new Error(RevertReason.RoundingError);
divisionByZeroErrorForCall = new LibMathRevertErrors.DivisionByZeroError();
roundingErrorForCall = new LibMathRevertErrors.RoundingError();
});
async function referenceIsRoundingErrorFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
if (numerator.eq(0)) {
return false;
}
if (target.eq(0)) {
return false;
}
const product = numerator.multipliedBy(target);
const remainder = product.mod(denominator);
const remainderTimes1000 = remainder.multipliedBy('1000');
const isError = remainderTimes1000.gte(product);
if (product.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
if (remainderTimes1000.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return isError;
}
async function referenceIsRoundingErrorCeilAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
if (numerator.eq(0)) {
return false;
}
if (target.eq(0)) {
return false;
}
const product = numerator.multipliedBy(target);
const remainder = product.mod(denominator);
const error = denominator.minus(remainder).mod(denominator);
const errorTimes1000 = error.multipliedBy('1000');
const isError = errorTimes1000.gte(product);
if (product.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
if (errorTimes1000.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return isError;
}
async function referenceSafeGetPartialAmountFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
const isRoundingError = await referenceIsRoundingErrorFloorAsync(numerator, denominator, target);
if (isRoundingError) {
throw roundingErrorForCall;
}
const product = numerator.multipliedBy(target);
if (product.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return product.dividedToIntegerBy(denominator);
}
describe('getPartialAmountFloor', async () => {
async function referenceGetPartialAmountFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
const product = numerator.multipliedBy(target);
if (product.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return product.dividedToIntegerBy(denominator);
}
async function testGetPartialAmountFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
return testExchange.getPartialAmountFloor.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'getPartialAmountFloor',
referenceGetPartialAmountFloorAsync,
testGetPartialAmountFloorAsync,
[uint256Values, uint256Values, uint256Values],
);
});
describe('getPartialAmountCeil', async () => {
async function referenceGetPartialAmountCeilAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
const product = numerator.multipliedBy(target);
const offset = product.plus(denominator.minus(1));
if (offset.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
const result = offset.dividedToIntegerBy(denominator);
if (product.mod(denominator).eq(0)) {
expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product);
} else {
expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product);
}
return result;
}
async function testGetPartialAmountCeilAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
return testExchange.getPartialAmountCeil.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'getPartialAmountCeil',
referenceGetPartialAmountCeilAsync,
testGetPartialAmountCeilAsync,
[uint256Values, uint256Values, uint256Values],
);
});
describe('safeGetPartialAmountFloor', async () => {
async function testSafeGetPartialAmountFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
return testExchange.safeGetPartialAmountFloor.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'safeGetPartialAmountFloor',
referenceSafeGetPartialAmountFloorAsync,
testSafeGetPartialAmountFloorAsync,
[uint256Values, uint256Values, uint256Values],
);
});
describe('safeGetPartialAmountCeil', async () => {
async function referenceSafeGetPartialAmountCeilAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
const isRoundingError = await referenceIsRoundingErrorCeilAsync(numerator, denominator, target);
if (isRoundingError) {
throw roundingErrorForCall;
}
const product = numerator.multipliedBy(target);
const offset = product.plus(denominator.minus(1));
if (offset.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
const result = offset.dividedToIntegerBy(denominator);
if (product.mod(denominator).eq(0)) {
expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product);
} else {
expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product);
}
return result;
}
async function testSafeGetPartialAmountCeilAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
return testExchange.safeGetPartialAmountCeil.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'safeGetPartialAmountCeil',
referenceSafeGetPartialAmountCeilAsync,
testSafeGetPartialAmountCeilAsync,
[uint256Values, uint256Values, uint256Values],
);
});
describe('isRoundingErrorFloor', async () => {
async function testIsRoundingErrorFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
return testExchange.isRoundingErrorFloor.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'isRoundingErrorFloor',
referenceIsRoundingErrorFloorAsync,
testIsRoundingErrorFloorAsync,
[uint256Values, uint256Values, uint256Values],
);
});
describe('isRoundingErrorCeil', async () => {
async function testIsRoundingErrorCeilAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
return testExchange.isRoundingErrorCeil.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'isRoundingErrorCeil',
referenceIsRoundingErrorCeilAsync,
testIsRoundingErrorCeilAsync,
[uint256Values, uint256Values, uint256Values],
);
});
});
// TODO(dorothy-zbornak): Add _settleOrder, _dispatchTransferFrom
blockchainTests.resets('Exchange core internal functions', env => {
let chainId: number;
// TODO(dorothy-zbornak): Add _settleOrder
blockchainTests.only('Exchange core internal functions', env => {
const CHAIN_ID = 1337;
const EMPTY_ORDER: Order = {
senderAddress: constants.NULL_ADDRESS,
makerAddress: constants.NULL_ADDRESS,
takerAddress: constants.NULL_ADDRESS,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
makerAssetAmount: constants.ZERO_AMOUNT,
takerAssetAmount: constants.ZERO_AMOUNT,
makerAssetData: constants.NULL_BYTES,
takerAssetData: constants.NULL_BYTES,
makerFeeAssetData: constants.NULL_BYTES,
takerFeeAssetData: constants.NULL_BYTES,
salt: constants.ZERO_AMOUNT,
feeRecipientAddress: constants.NULL_ADDRESS,
expirationTimeSeconds: constants.ZERO_AMOUNT,
};
let testExchange: TestExchangeInternalsContract;
let safeMathErrorForSendTransaction: Error | undefined;
let divisionByZeroErrorForCall: Error | undefined;
let roundingErrorForCall: Error | undefined;
let logDecoder: LogDecoder;
let senderAddress: string;
before(async () => {
chainId = await providerUtils.getChainIdAsync(env.provider);
emptyOrder.domain.chainId = chainId;
emptySignedOrder.domain.chainId = chainId;
[ senderAddress ] = await env.getAccountAddressesAsync();
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
artifacts.TestExchangeInternals,
env.provider,
env.txDefaults,
new BigNumber(chainId),
);
divisionByZeroErrorForCall = new Error(RevertReason.DivisionByZero);
roundingErrorForCall = new Error(RevertReason.RoundingError);
safeMathErrorForSendTransaction = safeMathErrorForCall;
divisionByZeroErrorForCall = new LibMathRevertErrors.DivisionByZeroError();
roundingErrorForCall = new LibMathRevertErrors.RoundingError();
});
// Note(albrow): Don't forget to add beforeEach and afterEach calls to reset
// the blockchain state for any tests which modify it!
async function referenceIsRoundingErrorFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
if (numerator.eq(0)) {
return false;
}
if (target.eq(0)) {
return false;
}
const product = numerator.multipliedBy(target);
const remainder = product.mod(denominator);
const remainderTimes1000 = remainder.multipliedBy('1000');
const isError = remainderTimes1000.gte(product);
if (product.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
if (remainderTimes1000.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return isError;
}
async function referenceSafeGetPartialAmountFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
if (denominator.eq(0)) {
throw divisionByZeroErrorForCall;
}
const isRoundingError = await referenceIsRoundingErrorFloorAsync(numerator, denominator, target);
if (isRoundingError) {
throw roundingErrorForCall;
}
const product = numerator.multipliedBy(target);
if (product.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return product.dividedToIntegerBy(denominator);
}
// TODO(dorothy-zbornak): Move this to `exchange-libs`.
describe('addFillResults', async () => {
function makeFillResults(value: BigNumber): FillResults {
return {
makerAssetFilledAmount: value,
takerAssetFilledAmount: value,
makerFeePaid: value,
takerFeePaid: value,
};
}
async function referenceAddFillResultsAsync(
totalValue: BigNumber,
singleValue: BigNumber,
): Promise<FillResults> {
// Note(albrow): Here, each of totalFillResults and
// singleFillResults will consist of fields with the same values.
// This should be safe because none of the fields in a given
// FillResults are ever used together in a mathemetical operation.
// They are only used with the corresponding field from *the other*
// FillResults, which are different.
const totalFillResults = makeFillResults(totalValue);
const singleFillResults = makeFillResults(singleValue);
// HACK(albrow): _.mergeWith mutates the first argument! To
// workaround this we use _.cloneDeep.
return _.mergeWith(
_.cloneDeep(totalFillResults),
singleFillResults,
(totalVal: BigNumber, singleVal: BigNumber) => {
const newTotal = totalVal.plus(singleVal);
if (newTotal.isGreaterThan(MAX_UINT256)) {
throw safeMathErrorForCall;
}
return newTotal;
},
);
}
async function testAddFillResultsAsync(totalValue: BigNumber, singleValue: BigNumber): Promise<FillResults> {
const totalFillResults = makeFillResults(totalValue);
const singleFillResults = makeFillResults(singleValue);
return testExchange.addFillResults.callAsync(totalFillResults, singleFillResults);
}
await testCombinatoriallyWithReferenceFuncAsync(
'addFillResults',
referenceAddFillResultsAsync,
testAddFillResultsAsync,
[uint256Values, uint256Values],
new BigNumber(CHAIN_ID),
);
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
});
describe('calculateFillResults', async () => {
blockchainTests('calculateFillResults', async () => {
function makeOrder(
makerAssetAmount: BigNumber,
takerAssetAmount: BigNumber,
@@ -428,13 +62,14 @@ blockchainTests.resets('Exchange core internal functions', env => {
takerFee: BigNumber,
): Order {
return {
...emptyOrder,
...EMPTY_ORDER,
makerAssetAmount,
takerAssetAmount,
makerFee,
takerFee,
};
}
async function referenceCalculateFillResultsAsync(
orderTakerAssetAmount: BigNumber,
takerAssetFilledAmount: BigNumber,
@@ -446,28 +81,12 @@ blockchainTests.resets('Exchange core internal functions', env => {
// in any mathematical operation in either the reference TypeScript
// implementation or the Solidity implementation of
// calculateFillResults.
const makerAssetFilledAmount = await referenceSafeGetPartialAmountFloorAsync(
return ReferenceFunctions.calculateFillResults(
makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount),
takerAssetFilledAmount,
orderTakerAssetAmount,
otherAmount,
);
const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount);
const orderMakerAssetAmount = order.makerAssetAmount;
return {
makerAssetFilledAmount,
takerAssetFilledAmount,
makerFeePaid: await referenceSafeGetPartialAmountFloorAsync(
makerAssetFilledAmount,
orderMakerAssetAmount,
otherAmount,
),
takerFeePaid: await referenceSafeGetPartialAmountFloorAsync(
takerAssetFilledAmount,
orderTakerAssetAmount,
otherAmount,
),
};
}
async function testCalculateFillResultsAsync(
orderTakerAssetAmount: BigNumber,
takerAssetFilledAmount: BigNumber,
@@ -476,57 +95,126 @@ blockchainTests.resets('Exchange core internal functions', env => {
const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount);
return testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount);
}
await testCombinatoriallyWithReferenceFuncAsync(
'calculateFillResults',
referenceCalculateFillResultsAsync,
testCalculateFillResultsAsync,
[uint256Values, uint256Values, uint256Values],
);
describe.optional('combinatorial tests', () => {
testCombinatoriallyWithReferenceFunc(
'calculateFillResults',
referenceCalculateFillResultsAsync,
testCalculateFillResultsAsync,
[uint256Values, uint256Values, uint256Values],
);
});
});
blockchainTests.resets('updateFilledState', async ({ web3Wrapper }) => {
async function referenceUpdateFilledStateAsync(
takerAssetFilledAmount: BigNumber,
orderTakerAssetFilledAmount: BigNumber,
// tslint:disable-next-line:no-unused-variable
orderHash: string,
): Promise<BigNumber> {
const totalFilledAmount = takerAssetFilledAmount.plus(orderTakerAssetFilledAmount);
if (totalFilledAmount.isGreaterThan(MAX_UINT256)) {
// FIXME throw safeMathErrorForSendTransaction(takerAssetFilledAmount, orderTakerAssetFilledAmount);
throw safeMathErrorForSendTransaction;
}
return totalFilledAmount;
blockchainTests.resets('updateFilledState', async () => {
const ONE_ETHER = new BigNumber(1e18);
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const randomHash = () => hexRandom(constants.WORD_LENGTH);
const randomAssetData = () => hexRandom(36);
const ORDER_DEFAULTS = {
senderAddress: randomAddress(),
makerAddress: randomAddress(),
takerAddress: randomAddress(),
makerFee: ONE_ETHER.times(0.001),
takerFee: ONE_ETHER.times(0.003),
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER.times(0.5),
makerAssetData: randomAssetData(),
takerAssetData: randomAssetData(),
makerFeeAssetData: randomAssetData(),
takerFeeAssetData: randomAssetData(),
salt: new BigNumber(_.random(0, 1e8)),
feeRecipientAddress: randomAddress(),
expirationTimeSeconds: new BigNumber(_.random(0, 1e8)),
};
function makeOrder(details?: Partial<Order>): Order {
return _.assign(
{},
ORDER_DEFAULTS,
details,
);
}
async function testUpdateFilledStateAsync(
takerAssetFilledAmount: BigNumber,
order: Order,
orderTakerAssetFilledAmount: BigNumber,
orderHash: string,
): Promise<BigNumber> {
const fillResults = {
makerAssetFilledAmount: new BigNumber(0),
takerAssetFilledAmount,
makerFeePaid: new BigNumber(0),
takerFeePaid: new BigNumber(0),
};
await web3Wrapper.awaitTransactionSuccessAsync(
await testExchange.updateFilledState.sendTransactionAsync(
emptySignedOrder,
constants.NULL_ADDRESS,
takerAddress: string,
takerAssetFillAmount: BigNumber,
): Promise<void> {
const orderHash = randomHash();
const fillResults = ReferenceFunctions.calculateFillResults(
order,
takerAssetFillAmount,
);
const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount);
// CAll `testUpdateFilledState()`, which will set the `filled`
// state for this order to `orderTakerAssetFilledAmount` before
// calling `_updateFilledState()`.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await testExchange.testUpdateFilledState.sendTransactionAsync(
order,
takerAddress,
orderHash,
orderTakerAssetFilledAmount,
fillResults,
),
constants.AWAIT_TRANSACTION_MINED_MS,
);
return testExchange.filled.callAsync(orderHash);
// Grab the new `filled` state for this order.
const actualFilledState = await testExchange.filled.callAsync(orderHash);
// Assert the `filled` state for this order.
expect(actualFilledState).to.bignumber.eq(expectedFilledState);
// Assert the logs.
const fillEvent = receipt.logs[0] as LogWithDecodedArgs<TestExchangeInternalsFillEventArgs>;
expect(fillEvent.event).to.eq('Fill');
expect(fillEvent.args.makerAddress).to.eq(order.makerAddress);
expect(fillEvent.args.feeRecipientAddress).to.eq(order.feeRecipientAddress);
expect(fillEvent.args.makerAssetData).to.eq(order.makerAssetData);
expect(fillEvent.args.takerAssetData).to.eq(order.takerAssetData);
expect(fillEvent.args.makerFeeAssetData).to.eq(order.makerFeeAssetData);
expect(fillEvent.args.takerFeeAssetData).to.eq(order.takerFeeAssetData);
expect(fillEvent.args.makerAssetFilledAmount).to.bignumber.eq(fillResults.makerAssetFilledAmount);
expect(fillEvent.args.takerAssetFilledAmount).to.bignumber.eq(fillResults.takerAssetFilledAmount);
expect(fillEvent.args.makerFeePaid).to.bignumber.eq(fillResults.makerFeePaid);
expect(fillEvent.args.takerFeePaid).to.bignumber.eq(fillResults.takerFeePaid);
expect(fillEvent.args.takerAddress).to.eq(takerAddress);
expect(fillEvent.args.senderAddress).to.eq(senderAddress);
expect(fillEvent.args.orderHash).to.eq(orderHash);
}
await testCombinatoriallyWithReferenceFuncAsync(
'updateFilledState',
referenceUpdateFilledStateAsync,
testUpdateFilledStateAsync,
[uint256Values, uint256Values, bytes32Values],
);
it('emits a `Fill` event and updates `filled` state correctly', async () => {
const order = makeOrder();
return testUpdateFilledStateAsync(
order,
order.takerAssetAmount.times(0.1),
randomAddress(),
order.takerAssetAmount.times(0.25),
);
});
it('throws if `leftOrderTakerAssetFilledAmount + fillResults.takerAssetFilledAmount` overflows', async () => {
const order = makeOrder();
const orderTakerAssetFilledAmount = constants.MAX_UINT256.dividedToIntegerBy(2);
const takerAssetFillAmount = constants.MAX_UINT256.dividedToIntegerBy(2).plus(2);
const fillResults = {
makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: takerAssetFillAmount,
makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: constants.ZERO_AMOUNT,
};
const expectedError = new SafeMathRevertErrors.SafeMathError(
SafeMathRevertErrors.SafeMathErrorCodes.Uint256AdditionOverflow,
orderTakerAssetFilledAmount,
takerAssetFillAmount,
);
return expect(testExchange.testUpdateFilledState.awaitTransactionSuccessAsync(
order,
randomAddress(),
randomHash(),
orderTakerAssetFilledAmount,
fillResults,
)).to.revertWith(expectedError);
});
});
});
// tslint:disable-line:max-file-line-count

View File

@@ -9,6 +9,8 @@ import { FillResults, OrderInfo, OrderStatus, SignatureType } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { calculateFillResults } from '../src/reference_functions';
import {
AssetBalances,
createBadAssetData,
@@ -18,9 +20,8 @@ import {
IsolatedExchangeWrapper,
Order,
} from './utils/isolated_exchange_wrapper';
import { calculateFillResults } from './utils/reference_functions';
blockchainTests.only('Isolated fillOrder() tests', env => {
blockchainTests('Isolated fillOrder() tests', env => {
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
const getCurrentTime = () => Math.floor(_.now() / 1000);
const { ZERO_AMOUNT } = constants;

View File

@@ -11,6 +11,7 @@ import {
import { ERC1155Contract as ERC1155TokenContract, Erc1155Wrapper as ERC1155Wrapper } from '@0x/contracts-erc1155';
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs';
import {
chaiSetup,
constants,
@@ -34,7 +35,6 @@ import {
ExchangeContract,
ExchangeWrapper,
ReentrantERC20TokenContract,
TestExchangeMathContract,
} from '../src';
import { MatchOrderTester, TokenBalances } from './utils/match_order_tester';
@@ -42,6 +42,7 @@ import { MatchOrderTester, TokenBalances } from './utils/match_order_tester';
const ZERO = new BigNumber(0);
const ONE = new BigNumber(1);
const TWO = new BigNumber(2);
const { isRoundingErrorCeil, isRoundingErrorFloor } = LibReferenceFunctions;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure();
@@ -88,8 +89,6 @@ describe('matchOrders', () => {
let matchOrderTester: MatchOrderTester;
let testExchangeMath: TestExchangeMathContract;
before(async () => {
await blockchainLifecycle.startAsync();
});
@@ -240,11 +239,6 @@ describe('matchOrders', () => {
orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
testExchangeMath = await TestExchangeMathContract.deployFrom0xArtifactAsync(
artifacts.TestExchangeMath,
provider,
txDefaults,
);
// Create match order tester
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, erc1155ProxyWrapper);
tokenBalances = await matchOrderTester.getBalancesAsync();
@@ -276,18 +270,18 @@ describe('matchOrders', () => {
const numerator = signedOrderLeft.makerAssetAmount;
const denominator = signedOrderLeft.takerAssetAmount;
const target = signedOrderRight.makerAssetAmount;
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
const _isRoundingErrorCeil = isRoundingErrorCeil(
numerator,
denominator,
target,
);
expect(isRoundingErrorCeil).to.be.true();
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
expect(_isRoundingErrorCeil).to.be.true();
const _isRoundingErrorFloor = isRoundingErrorFloor(
numerator,
denominator,
target,
);
expect(isRoundingErrorFloor).to.be.false();
expect(_isRoundingErrorFloor).to.be.false();
// Match signedOrderLeft with signedOrderRight
// Note that the left maker received a slightly better sell price.
// This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults.
@@ -342,20 +336,19 @@ describe('matchOrders', () => {
const numerator = signedOrderRight.takerAssetAmount;
const denominator = signedOrderRight.makerAssetAmount;
const target = signedOrderLeft.takerAssetAmount;
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
const _isRoundingErrorFloor = isRoundingErrorFloor(
numerator,
denominator,
target,
);
expect(isRoundingErrorFloor).to.be.true();
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
expect(_isRoundingErrorFloor).to.be.true();
const _isRoundingErrorCeil = isRoundingErrorCeil(
numerator,
denominator,
target,
);
expect(isRoundingErrorCeil).to.be.false();
// Match signedOrderLeft with signedOrderRight
// Note that the right maker received a slightly better purchase price.
expect(_isRoundingErrorCeil).to.be.false();
// Match signedOrderLeft isRoundingErrorFloor right maker received a slightly better purchase price.
// This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults.
// Because the right maker received a slightly more favorable buy price, the fee
// paid by the right taker is slightly higher than that paid by the right maker.
@@ -1421,18 +1414,18 @@ describe('matchOrders', () => {
const numerator = signedOrderLeft.makerAssetAmount;
const denominator = signedOrderLeft.takerAssetAmount;
const target = signedOrderRight.makerAssetAmount;
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
const _isRoundingErrorCeil = isRoundingErrorCeil(
numerator,
denominator,
target,
);
expect(isRoundingErrorCeil).to.be.true();
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
expect(_isRoundingErrorCeil).to.be.true();
const _isRoundingErrorFloor = isRoundingErrorFloor(
numerator,
denominator,
target,
);
expect(isRoundingErrorFloor).to.be.false();
expect(_isRoundingErrorFloor).to.be.false();
// Match signedOrderLeft with signedOrderRight
// Note that the left maker received a slightly better sell price.
// This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults.
@@ -1487,18 +1480,18 @@ describe('matchOrders', () => {
const numerator = signedOrderRight.makerAssetAmount;
const denominator = signedOrderRight.takerAssetAmount;
const target = signedOrderLeft.makerAssetAmount;
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
const _isRoundingErrorCeil = isRoundingErrorCeil(
numerator,
denominator,
target,
);
expect(isRoundingErrorCeil).to.be.false();
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
expect(_isRoundingErrorCeil).to.be.false();
const _isRoundingErrorFloor = isRoundingErrorFloor(
numerator,
denominator,
target,
);
expect(isRoundingErrorFloor).to.be.false();
expect(_isRoundingErrorFloor).to.be.false();
// Match signedOrderLeft with signedOrderRight
// Note that the right maker received a slightly better purchase price.
// This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults.

View File

@@ -156,7 +156,7 @@ export function createBadSignature(type: SignatureType = SignatureType.EIP712):
return `0x00${Buffer.from([type]).toString('hex')}`;
}
const ERC20_ASSET_DATA_LENGTH = 24;
const ERC20_ASSET_DATA_LENGTH = 36;
/**
* Create asset data for the `IsolatedExchange` contract that will pass.

View File

@@ -18,7 +18,6 @@
"generated-artifacts/ReentrantERC20Token.json",
"generated-artifacts/TestAssetProxyDispatcher.json",
"generated-artifacts/TestExchangeInternals.json",
"generated-artifacts/TestExchangeMath.json",
"generated-artifacts/TestLibExchangeRichErrorDecoder.json",
"generated-artifacts/TestSignatureValidator.json",
"generated-artifacts/TestValidatorWallet.json",