@0x:contracts-exchange Addressed review feedback

This commit is contained in:
Alex Towle
2019-08-29 00:31:30 -07:00
parent 75e6c45285
commit dba0d8469d
34 changed files with 510 additions and 291 deletions

View File

@@ -52,13 +52,18 @@ library LibFillResults {
/// @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.
/// @param protocolFeeMultiplier The current protocol fee of the exchange contract.
/// @param gasPrice The gasprice of the transaction. This is provided so that the function call can continue
/// to be pure rather than view.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function calculateFillResults(
LibOrder.Order memory order,
uint256 takerAssetFilledAmount
uint256 takerAssetFilledAmount,
uint256 protocolFeeMultiplier,
uint256 gasPrice
)
internal
view
pure
returns (FillResults memory fillResults)
{
// Compute proportional transfer amounts
@@ -79,6 +84,9 @@ library LibFillResults {
order.takerFee
);
// Compute the protocol fee that should be paid for a single fill.
fillResults.protocolFeePaid = gasPrice.safeMul(protocolFeeMultiplier);
return fillResults;
}
@@ -90,6 +98,9 @@ library LibFillResults {
/// @param rightOrder Second order to match.
/// @param leftOrderTakerAssetFilledAmount Amount of left order already filled.
/// @param rightOrderTakerAssetFilledAmount Amount of right order already filled.
/// @param protocolFeeMultiplier The current protocol fee of the exchange contract.
/// @param gasPrice The gasprice of the transaction. This is provided so that the function call can continue
/// to be pure rather than view.
/// @param shouldMaximallyFillOrders A value that indicates whether or not this calculation should use
/// the maximal fill order matching strategy.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
@@ -98,10 +109,12 @@ library LibFillResults {
LibOrder.Order memory rightOrder,
uint256 leftOrderTakerAssetFilledAmount,
uint256 rightOrderTakerAssetFilledAmount,
uint256 protocolFeeMultiplier,
uint256 gasPrice,
bool shouldMaximallyFillOrders
)
internal
view
pure
returns (MatchedFillResults memory matchedFillResults)
{
// Derive maker asset amounts for left & right orders, given store taker assert amounts
@@ -163,6 +176,12 @@ library LibFillResults {
rightOrder.takerFee
);
// Compute the protocol fee that should be paid for a single fill. In this
// case this should be made the protocol fee for both the left and right orders.
uint256 protocolFee = gasPrice.safeMul(protocolFeeMultiplier);
matchedFillResults.left.protocolFeePaid = protocolFee;
matchedFillResults.right.protocolFeePaid = protocolFee;
// Return fill results
return matchedFillResults;
}

View File

@@ -29,13 +29,20 @@ contract TestLibFillResults {
function calculateFillResults(
LibOrder.Order memory order,
uint256 takerAssetFilledAmount
uint256 takerAssetFilledAmount,
uint256 protocolFeeMultiplier,
uint256 gasPrice
)
public
view
pure
returns (LibFillResults.FillResults memory fillResults)
{
fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount);
fillResults = LibFillResults.calculateFillResults(
order,
takerAssetFilledAmount,
protocolFeeMultiplier,
gasPrice
);
return fillResults;
}
@@ -44,10 +51,12 @@ contract TestLibFillResults {
LibOrder.Order memory rightOrder,
uint256 leftOrderTakerAssetFilledAmount,
uint256 rightOrderTakerAssetFilledAmount,
uint256 protocolFeeMultiplier,
uint256 gasPrice,
bool shouldMaximallyFillOrders
)
public
view
pure
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
matchedFillResults = LibFillResults.calculateMatchedFillResults(
@@ -55,6 +64,8 @@ contract TestLibFillResults {
rightOrder,
leftOrderTakerAssetFilledAmount,
rightOrderTakerAssetFilledAmount,
protocolFeeMultiplier,
gasPrice,
shouldMaximallyFillOrders
);
return matchedFillResults;

View File

@@ -1,4 +1,3 @@
import { constants } from '@0x/contracts-test-utils';
import { ReferenceFunctions } from '@0x/contracts-utils';
import { LibMathRevertErrors } from '@0x/order-utils';
import { FillResults, OrderWithoutDomain } from '@0x/types';
@@ -94,7 +93,12 @@ export function addFillResults(a: FillResults, b: FillResults): FillResults {
/**
* Calculates amounts filled and fees paid by maker and taker.
*/
export function calculateFillResults(order: OrderWithoutDomain, takerAssetFilledAmount: BigNumber): FillResults {
export function calculateFillResults(
order: OrderWithoutDomain,
takerAssetFilledAmount: BigNumber,
protocolFeeMultiplier: BigNumber,
gasPrice: BigNumber,
): FillResults {
const makerAssetFilledAmount = safeGetPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
@@ -107,6 +111,6 @@ export function calculateFillResults(order: OrderWithoutDomain, takerAssetFilled
takerAssetFilledAmount,
makerFeePaid,
takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, // This field is not calculated in `calculateFillResults` as a gas optimization.
protocolFeePaid: safeMul(protocolFeeMultiplier, gasPrice),
};
}

View File

@@ -93,6 +93,8 @@ blockchainTests('LibFillResults', env => {
return ReferenceFunctions.calculateFillResults(
makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount),
takerAssetFilledAmount,
takerAssetFilledAmount, // Using this so that the gas price is distinct from protocolFeeMultiplier
otherAmount,
);
}
@@ -102,7 +104,12 @@ blockchainTests('LibFillResults', env => {
otherAmount: BigNumber,
): Promise<FillResults> {
const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount);
return libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount);
return libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
takerAssetFilledAmount, // Using this so that the gas price is distinct from protocolFeeMultiplier
otherAmount,
);
}
testCombinatoriallyWithReferenceFunc(
@@ -115,6 +122,9 @@ blockchainTests('LibFillResults', env => {
describe('explicit tests', () => {
const MAX_UINT256_ROOT = constants.MAX_UINT256_ROOT;
const DEFAULT_GAS_PRICE = new BigNumber(200000);
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
function makeOrder(details?: Partial<Order>): Order {
return _.assign({}, EMPTY_ORDER, details);
}
@@ -127,8 +137,18 @@ blockchainTests('LibFillResults', env => {
takerFee: ONE_ETHER.times(0.0025),
});
const takerAssetFilledAmount = ONE_ETHER.dividedToIntegerBy(3);
const expected = ReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount);
const actual = await libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount);
const expected = ReferenceFunctions.calculateFillResults(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
);
const actual = await libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
);
expect(actual).to.deep.eq(expected);
});
@@ -144,9 +164,14 @@ blockchainTests('LibFillResults', env => {
takerAssetFilledAmount,
order.makerAssetAmount,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if computing `fillResults.makerFeePaid` overflows', async () => {
@@ -167,9 +192,14 @@ blockchainTests('LibFillResults', env => {
makerAssetFilledAmount,
order.makerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if computing `fillResults.takerFeePaid` overflows', async () => {
@@ -185,9 +215,14 @@ blockchainTests('LibFillResults', env => {
takerAssetFilledAmount,
order.takerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if `order.makerAssetAmount` is 0', async () => {
@@ -197,9 +232,14 @@ blockchainTests('LibFillResults', env => {
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if `order.takerAssetAmount` is 0', async () => {
@@ -209,9 +249,14 @@ blockchainTests('LibFillResults', env => {
});
const takerAssetFilledAmount = ONE_ETHER;
const expectedError = new LibMathRevertErrors.DivisionByZeroError();
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', async () => {
@@ -225,9 +270,14 @@ blockchainTests('LibFillResults', env => {
order.takerAssetAmount,
order.makerAssetAmount,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if there is a rounding error computing `makerFeePaid`', async () => {
@@ -247,9 +297,91 @@ blockchainTests('LibFillResults', env => {
order.makerAssetAmount,
order.makerFee,
);
return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith(
expectedError,
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if there is a rounding error computing `takerFeePaid`', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER,
takerFee: new BigNumber(100),
});
const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const expectedError = new LibMathRevertErrors.RoundingError(
makerAssetFilledAmount,
order.makerAssetAmount,
order.takerFee,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if computing `fillResults.protocolFeePaid` overflows', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER.times(2),
makerFee: ONE_ETHER.times(0.0023),
takerFee: ONE_ETHER.times(0.0025),
});
const takerAssetFilledAmount = ONE_ETHER.dividedToIntegerBy(3);
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow,
DEFAULT_GAS_PRICE,
MAX_UINT256,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
MAX_UINT256,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
it('reverts if there is a rounding error computing `makerFeePaid`', async () => {
const order = makeOrder({
makerAssetAmount: ONE_ETHER,
takerAssetAmount: ONE_ETHER,
makerFee: new BigNumber(100),
});
const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3);
const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount,
);
const expectedError = new LibMathRevertErrors.RoundingError(
makerAssetFilledAmount,
order.makerAssetAmount,
order.makerFee,
);
return expect(
libsContract.calculateFillResults.callAsync(
order,
takerAssetFilledAmount,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
DEFAULT_GAS_PRICE,
),
).to.revertWith(expectedError);
});
});
});
@@ -358,14 +490,14 @@ blockchainTests('LibFillResults', env => {
takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16),
takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16),
protocolFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: Web3Wrapper.toBaseUnitAmount(15, 4),
},
right: {
makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18),
makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16),
takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16),
protocolFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: Web3Wrapper.toBaseUnitAmount(15, 4),
},
profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18),
profitInRightMakerAsset: constants.ZERO_AMOUNT,
@@ -401,6 +533,8 @@ blockchainTests('LibFillResults', env => {
rightOrder,
leftOrderTakerAssetFilledAmount,
rightOrderTakerAssetFilledAmount,
protocolFeeMultiplier,
gasPrice,
false,
{ from },
);
@@ -1072,6 +1206,8 @@ blockchainTests('LibFillResults', env => {
rightOrder,
leftOrderTakerAssetFilledAmount,
rightOrderTakerAssetFilledAmount,
protocolFeeMultiplier,
gasPrice,
true,
{ from },
);