Split up TestExchangeInternals into two contracts
This commit is contained in:
parent
1fe159f432
commit
073976de10
@ -39,6 +39,7 @@
|
||||
"test/ReentrantERC20Token.sol",
|
||||
"test/TestAssetProxyDispatcher.sol",
|
||||
"test/TestExchangeInternals.sol",
|
||||
"test/TestExchangeMath.sol",
|
||||
"test/TestLibExchangeRichErrorDecoder.sol",
|
||||
"test/TestSignatureValidator.sol",
|
||||
"test/TestValidatorWallet.sol"
|
||||
|
@ -172,6 +172,7 @@ contract MixinMatchOrders is
|
||||
/// @dev Match complementary orders that have a profitable spread.
|
||||
/// Each order is filled at their respective price point, and
|
||||
/// the matcher receives a profit denominated in the left maker asset.
|
||||
/// This is the reentrant version of `batchMatchOrders` and `batchMatchOrdersWithMaximalFill`.
|
||||
/// @param leftOrders Set of orders with the same maker / taker asset.
|
||||
/// @param rightOrders Set of orders to match against `leftOrders`
|
||||
/// @param leftSignatures Proof that left orders were created by the left makers.
|
||||
@ -671,7 +672,8 @@ contract MixinMatchOrders is
|
||||
/// Each order is filled at their respective price point. However, the calculations are
|
||||
/// carried out as though the orders are both being filled at the right order's price point.
|
||||
/// The profit made by the left order goes to the taker (who matched the two orders). This
|
||||
/// function is needed to allow for reentrant order matching (used by batchMatchOrders).
|
||||
/// function is needed to allow for reentrant order matching (used by `batchMatchOrders` and
|
||||
/// `batchMatchOrdersWithMaximalFill`).
|
||||
/// @param leftOrder First order to match.
|
||||
/// @param rightOrder Second order to match.
|
||||
/// @param leftSignature Proof that order was created by the left maker.
|
||||
|
@ -42,24 +42,6 @@ contract IMatchOrders {
|
||||
public
|
||||
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults);
|
||||
|
||||
/// @dev Match two complementary orders that have a profitable spread.
|
||||
/// Each order is filled at their respective price point. However, the calculations are
|
||||
/// carried out as though the orders are both being filled at the right order's price point.
|
||||
/// The profit made by the left order goes to the taker (who matched the two orders).
|
||||
/// @param leftOrder First order to match.
|
||||
/// @param rightOrder Second order to match.
|
||||
/// @param leftSignature Proof that order was created by the left maker.
|
||||
/// @param rightSignature Proof that order was created by the right maker.
|
||||
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
|
||||
function matchOrders(
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder,
|
||||
bytes memory leftSignature,
|
||||
bytes memory rightSignature
|
||||
)
|
||||
public
|
||||
returns (LibFillResults.MatchedFillResults memory matchedFillResults);
|
||||
|
||||
/// @dev Calculates fill amounts for the matched orders.
|
||||
/// Each order is filled at their respective price point. However, the calculations are
|
||||
/// carried out as though the orders are both being filled at the right order's price point.
|
||||
@ -79,6 +61,24 @@ contract IMatchOrders {
|
||||
pure
|
||||
returns (LibFillResults.MatchedFillResults memory matchedFillResults);
|
||||
|
||||
/// @dev Match two complementary orders that have a profitable spread.
|
||||
/// Each order is filled at their respective price point. However, the calculations are
|
||||
/// carried out as though the orders are both being filled at the right order's price point.
|
||||
/// The profit made by the left order goes to the taker (who matched the two orders).
|
||||
/// @param leftOrder First order to match.
|
||||
/// @param rightOrder Second order to match.
|
||||
/// @param leftSignature Proof that order was created by the left maker.
|
||||
/// @param rightSignature Proof that order was created by the right maker.
|
||||
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
|
||||
function matchOrders(
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder,
|
||||
bytes memory leftSignature,
|
||||
bytes memory rightSignature
|
||||
)
|
||||
public
|
||||
returns (LibFillResults.MatchedFillResults memory matchedFillResults);
|
||||
|
||||
/// @dev Match two complementary orders that have a profitable spread.
|
||||
/// Each order is maximally filled at their respective price point, and
|
||||
/// the matcher receives a profit denominated in either the left maker asset,
|
||||
|
@ -62,110 +62,6 @@ contract TestExchangeInternals is
|
||||
return _calculateFillResults(order, takerAssetFilledAmount);
|
||||
}
|
||||
|
||||
/// @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);
|
||||
}
|
||||
|
||||
/// @dev Updates state with results of a fill order.
|
||||
/// @param order that was filled.
|
||||
/// @param takerAddress Address of taker who filled the order.
|
||||
|
131
contracts/exchange/contracts/test/TestExchangeMath.sol
Normal file
131
contracts/exchange/contracts/test/TestExchangeMath.sol
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -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|ReentrantERC20Token|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|Whitelist).json",
|
||||
"abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|IWallet|IWrapperFunctions|ReentrantERC20Token|TestAssetProxyDispatcher|TestExchangeInternals|TestExchangeMath|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|Whitelist).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
|
@ -19,6 +19,7 @@ import * as IWrapperFunctions from '../generated-artifacts/IWrapperFunctions.jso
|
||||
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';
|
||||
@ -28,17 +29,18 @@ export const artifacts = {
|
||||
Whitelist: Whitelist as ContractArtifact,
|
||||
Exchange: Exchange as ContractArtifact,
|
||||
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
|
||||
IEIP1271Wallet: IEIP1271Wallet as ContractArtifact,
|
||||
IExchange: IExchange as ContractArtifact,
|
||||
IExchangeCore: IExchangeCore as ContractArtifact,
|
||||
IMatchOrders: IMatchOrders as ContractArtifact,
|
||||
ISignatureValidator: ISignatureValidator as ContractArtifact,
|
||||
ITransactions: ITransactions as ContractArtifact,
|
||||
IWallet: IWallet as ContractArtifact,
|
||||
IEIP1271Wallet: IEIP1271Wallet as ContractArtifact,
|
||||
IWrapperFunctions: IWrapperFunctions as ContractArtifact,
|
||||
ReentrantERC20Token: ReentrantERC20Token as ContractArtifact,
|
||||
TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact,
|
||||
TestExchangeInternals: TestExchangeInternals as ContractArtifact,
|
||||
TestExchangeMath: TestExchangeMath as ContractArtifact,
|
||||
TestLibExchangeRichErrorDecoder: TestLibExchangeRichErrorDecoder as ContractArtifact,
|
||||
TestSignatureValidator: TestSignatureValidator as ContractArtifact,
|
||||
TestValidatorWallet: TestValidatorWallet as ContractArtifact,
|
||||
|
@ -17,6 +17,7 @@ export * from '../generated-wrappers/i_wrapper_functions';
|
||||
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';
|
||||
|
@ -16,7 +16,7 @@ import { BigNumber, providerUtils, SafeMathRevertErrors } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, TestExchangeInternalsContract } from '../src';
|
||||
import { artifacts, TestExchangeInternalsContract, TestExchangeMathContract } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@ -53,10 +53,9 @@ const emptySignedOrder: SignedOrder = {
|
||||
|
||||
const safeMathErrorForCall = new SafeMathRevertErrors.SafeMathError();
|
||||
|
||||
describe('Exchange core internal functions', () => {
|
||||
describe('Exchange math internal functions', () => {
|
||||
let chainId: number;
|
||||
let testExchange: TestExchangeInternalsContract;
|
||||
let safeMathErrorForSendTransaction: Error | undefined;
|
||||
let testExchange: TestExchangeMathContract;
|
||||
let divisionByZeroErrorForCall: Error | undefined;
|
||||
let roundingErrorForCall: Error | undefined;
|
||||
|
||||
@ -71,15 +70,13 @@ describe('Exchange core internal functions', () => {
|
||||
emptyOrder.domain.chainId = chainId;
|
||||
emptySignedOrder.domain.chainId = chainId;
|
||||
|
||||
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeInternals,
|
||||
testExchange = await TestExchangeMathContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeMath,
|
||||
provider,
|
||||
txDefaults,
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
divisionByZeroErrorForCall = new Error(RevertReason.DivisionByZero);
|
||||
roundingErrorForCall = new Error(RevertReason.RoundingError);
|
||||
safeMathErrorForSendTransaction = safeMathErrorForCall;
|
||||
divisionByZeroErrorForCall = new LibMathRevertErrors.DivisionByZeroError();
|
||||
roundingErrorForCall = new LibMathRevertErrors.RoundingError();
|
||||
});
|
||||
@ -160,118 +157,6 @@ describe('Exchange core internal functions', () => {
|
||||
return product.dividedToIntegerBy(denominator);
|
||||
}
|
||||
|
||||
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],
|
||||
);
|
||||
});
|
||||
|
||||
describe('calculateFillResults', async () => {
|
||||
function makeOrder(
|
||||
makerAssetAmount: BigNumber,
|
||||
takerAssetAmount: BigNumber,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
): Order {
|
||||
return {
|
||||
...emptyOrder,
|
||||
makerAssetAmount,
|
||||
takerAssetAmount,
|
||||
makerFee,
|
||||
takerFee,
|
||||
};
|
||||
}
|
||||
async function referenceCalculateFillResultsAsync(
|
||||
orderTakerAssetAmount: BigNumber,
|
||||
takerAssetFilledAmount: BigNumber,
|
||||
otherAmount: BigNumber,
|
||||
): Promise<FillResults> {
|
||||
// Note(albrow): Here we are re-using the same value (otherAmount)
|
||||
// for order.makerAssetAmount, order.makerFee, and order.takerFee.
|
||||
// This should be safe because they are never used with each other
|
||||
// in any mathematical operation in either the reference TypeScript
|
||||
// implementation or the Solidity implementation of
|
||||
// calculateFillResults.
|
||||
const makerAssetFilledAmount = await referenceSafeGetPartialAmountFloorAsync(
|
||||
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,
|
||||
otherAmount: BigNumber,
|
||||
): Promise<FillResults> {
|
||||
const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount);
|
||||
return testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount);
|
||||
}
|
||||
await testCombinatoriallyWithReferenceFuncAsync(
|
||||
'calculateFillResults',
|
||||
referenceCalculateFillResultsAsync,
|
||||
testCalculateFillResultsAsync,
|
||||
[uint256Values, uint256Values, uint256Values],
|
||||
);
|
||||
});
|
||||
|
||||
describe('getPartialAmountFloor', async () => {
|
||||
async function referenceGetPartialAmountFloorAsync(
|
||||
numerator: BigNumber,
|
||||
@ -427,6 +312,198 @@ describe('Exchange core internal functions', () => {
|
||||
[uint256Values, uint256Values, uint256Values],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Exchange core internal functions', () => {
|
||||
let chainId: number;
|
||||
let testExchange: TestExchangeInternalsContract;
|
||||
let safeMathErrorForSendTransaction: Error | undefined;
|
||||
let divisionByZeroErrorForCall: Error | undefined;
|
||||
let roundingErrorForCall: Error | undefined;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
before(async () => {
|
||||
chainId = await providerUtils.getChainIdAsync(provider);
|
||||
emptyOrder.domain.chainId = chainId;
|
||||
emptySignedOrder.domain.chainId = chainId;
|
||||
|
||||
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeInternals,
|
||||
provider,
|
||||
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);
|
||||
}
|
||||
|
||||
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],
|
||||
);
|
||||
});
|
||||
|
||||
describe('calculateFillResults', async () => {
|
||||
function makeOrder(
|
||||
makerAssetAmount: BigNumber,
|
||||
takerAssetAmount: BigNumber,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
): Order {
|
||||
return {
|
||||
...emptyOrder,
|
||||
makerAssetAmount,
|
||||
takerAssetAmount,
|
||||
makerFee,
|
||||
takerFee,
|
||||
};
|
||||
}
|
||||
async function referenceCalculateFillResultsAsync(
|
||||
orderTakerAssetAmount: BigNumber,
|
||||
takerAssetFilledAmount: BigNumber,
|
||||
otherAmount: BigNumber,
|
||||
): Promise<FillResults> {
|
||||
// Note(albrow): Here we are re-using the same value (otherAmount)
|
||||
// for order.makerAssetAmount, order.makerFee, and order.takerFee.
|
||||
// This should be safe because they are never used with each other
|
||||
// in any mathematical operation in either the reference TypeScript
|
||||
// implementation or the Solidity implementation of
|
||||
// calculateFillResults.
|
||||
const makerAssetFilledAmount = await referenceSafeGetPartialAmountFloorAsync(
|
||||
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,
|
||||
otherAmount: BigNumber,
|
||||
): Promise<FillResults> {
|
||||
const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount);
|
||||
return testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount);
|
||||
}
|
||||
await testCombinatoriallyWithReferenceFuncAsync(
|
||||
'calculateFillResults',
|
||||
referenceCalculateFillResultsAsync,
|
||||
testCalculateFillResultsAsync,
|
||||
[uint256Values, uint256Values, uint256Values],
|
||||
);
|
||||
});
|
||||
|
||||
describe('updateFilledState', async () => {
|
||||
// Note(albrow): Since updateFilledState modifies the state by calling
|
||||
@ -481,3 +558,4 @@ describe('Exchange core internal functions', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
// tslint:disable-line:max-file-line-count
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
ExchangeContract,
|
||||
ExchangeWrapper,
|
||||
ReentrantERC20TokenContract,
|
||||
TestExchangeInternalsContract,
|
||||
TestExchangeMathContract,
|
||||
} from '../src';
|
||||
|
||||
import { MatchOrderTester, TokenBalances } from './utils/match_order_tester';
|
||||
@ -80,7 +80,7 @@ describe('matchOrders', () => {
|
||||
|
||||
let matchOrderTester: MatchOrderTester;
|
||||
|
||||
let testExchange: TestExchangeInternalsContract;
|
||||
let testExchangeMath: TestExchangeMathContract;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@ -232,11 +232,10 @@ describe('matchOrders', () => {
|
||||
orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
|
||||
const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
|
||||
orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
|
||||
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeInternals,
|
||||
testExchangeMath = await TestExchangeMathContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeMath,
|
||||
provider,
|
||||
txDefaults,
|
||||
new BigNumber(chainId),
|
||||
);
|
||||
// Create match order tester
|
||||
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, erc1155ProxyWrapper);
|
||||
@ -269,13 +268,13 @@ describe('matchOrders', () => {
|
||||
const numerator = signedOrderLeft.makerAssetAmount;
|
||||
const denominator = signedOrderLeft.takerAssetAmount;
|
||||
const target = signedOrderRight.makerAssetAmount;
|
||||
const isRoundingErrorCeil = await testExchange.isRoundingErrorCeil.callAsync(
|
||||
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
);
|
||||
expect(isRoundingErrorCeil).to.be.true();
|
||||
const isRoundingErrorFloor = await testExchange.isRoundingErrorFloor.callAsync(
|
||||
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
@ -335,13 +334,13 @@ describe('matchOrders', () => {
|
||||
const numerator = signedOrderRight.takerAssetAmount;
|
||||
const denominator = signedOrderRight.makerAssetAmount;
|
||||
const target = signedOrderLeft.takerAssetAmount;
|
||||
const isRoundingErrorFloor = await testExchange.isRoundingErrorFloor.callAsync(
|
||||
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
);
|
||||
expect(isRoundingErrorFloor).to.be.true();
|
||||
const isRoundingErrorCeil = await testExchange.isRoundingErrorCeil.callAsync(
|
||||
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
@ -1897,13 +1896,13 @@ describe('matchOrders', () => {
|
||||
const numerator = signedOrderLeft.makerAssetAmount;
|
||||
const denominator = signedOrderLeft.takerAssetAmount;
|
||||
const target = signedOrderRight.makerAssetAmount;
|
||||
const isRoundingErrorCeil = await testExchange.isRoundingErrorCeil.callAsync(
|
||||
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
);
|
||||
expect(isRoundingErrorCeil).to.be.true();
|
||||
const isRoundingErrorFloor = await testExchange.isRoundingErrorFloor.callAsync(
|
||||
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
@ -1963,13 +1962,13 @@ describe('matchOrders', () => {
|
||||
const numerator = signedOrderRight.makerAssetAmount;
|
||||
const denominator = signedOrderRight.takerAssetAmount;
|
||||
const target = signedOrderLeft.makerAssetAmount;
|
||||
const isRoundingErrorCeil = await testExchange.isRoundingErrorCeil.callAsync(
|
||||
const isRoundingErrorCeil = await testExchangeMath.isRoundingErrorCeil.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
);
|
||||
expect(isRoundingErrorCeil).to.be.false();
|
||||
const isRoundingErrorFloor = await testExchange.isRoundingErrorFloor.callAsync(
|
||||
const isRoundingErrorFloor = await testExchangeMath.isRoundingErrorFloor.callAsync(
|
||||
numerator,
|
||||
denominator,
|
||||
target,
|
||||
|
@ -17,6 +17,7 @@
|
||||
"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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user