aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/src
diff options
context:
space:
mode:
authorAmir Bandeali <abandeali1@gmail.com>2018-08-25 08:29:09 +0800
committerGitHub <noreply@github.com>2018-08-25 08:29:09 +0800
commit74ce893f520db6ae4617432975b5c9321ff5b89b (patch)
tree45853fe1c16066290834ee5827b3c269a9fb7734 /packages/contracts/src
parent94e01be9ed5bfe2d0ace4cd0562fced889b34108 (diff)
parentcc1fac9bbee2656bdb327490de42922abfc5125a (diff)
downloaddexon-0x-contracts-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.gz
dexon-0x-contracts-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.zst
dexon-0x-contracts-74ce893f520db6ae4617432975b5c9321ff5b89b.zip
Merge pull request #1003 from 0xProject/feature/contracts/roundup
[contracts] Add getPartialAmountCeil and isRoundingErrorCeil
Diffstat (limited to 'packages/contracts/src')
-rw-r--r--packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol4
-rw-r--r--packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol4
-rw-r--r--packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol2
-rw-r--r--packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol8
-rw-r--r--packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol4
-rw-r--r--packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol4
-rw-r--r--packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol122
-rw-r--r--packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol44
-rw-r--r--packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol42
9 files changed, 198 insertions, 36 deletions
diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol
index 218713d3c..a7ff400b9 100644
--- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol
+++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol
@@ -163,7 +163,7 @@ contract MixinExchangeWrapper is
// Convert the remaining amount of makerAsset to buy into remaining amount
// of takerAsset to sell, assuming entire amount can be sold in the current order
- uint256 remainingTakerAssetFillAmount = getPartialAmount(
+ uint256 remainingTakerAssetFillAmount = getPartialAmountFloor(
orders[i].takerAssetAmount,
orders[i].makerAssetAmount,
remainingMakerAssetFillAmount
@@ -231,7 +231,7 @@ contract MixinExchangeWrapper is
// Convert the remaining amount of ZRX to buy into remaining amount
// of WETH to sell, assuming entire amount can be sold in the current order.
- uint256 remainingWethSellAmount = getPartialAmount(
+ uint256 remainingWethSellAmount = getPartialAmountFloor(
orders[i].takerAssetAmount,
safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees
remainingZrxBuyAmount
diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol
index 42cec4d36..14f191879 100644
--- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol
+++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol
@@ -87,7 +87,7 @@ contract MixinForwarderCore is
uint256 makerAssetAmountPurchased;
if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) {
// Calculate amount of WETH that won't be spent on ETH fees.
- wethSellAmount = getPartialAmount(
+ wethSellAmount = getPartialAmountFloor(
PERCENTAGE_DENOMINATOR,
safeAdd(PERCENTAGE_DENOMINATOR, feePercentage),
msg.value
@@ -103,7 +103,7 @@ contract MixinForwarderCore is
makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid);
} else {
// 5% of WETH is reserved for filling feeOrders and paying feeRecipient.
- wethSellAmount = getPartialAmount(
+ wethSellAmount = getPartialAmountFloor(
MAX_WETH_FILL_PERCENTAGE,
PERCENTAGE_DENOMINATOR,
msg.value
diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol
index 93e85e599..5863b522d 100644
--- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol
+++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol
@@ -82,7 +82,7 @@ contract MixinWeth is
uint256 wethRemaining = safeSub(msg.value, wethSold);
// Calculate ETH fee to pay to feeRecipient.
- uint256 ethFee = getPartialAmount(
+ uint256 ethFee = getPartialAmountFloor(
feePercentage,
PERCENTAGE_DENOMINATOR,
wethSoldExcludingFeeOrders
diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol
index ab5c6e507..f3a0591b2 100644
--- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol
@@ -320,7 +320,7 @@ contract MixinExchangeCore is
// Validate fill order rounding
require(
- !isRoundingError(
+ !isRoundingErrorFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount
@@ -376,17 +376,17 @@ contract MixinExchangeCore is
{
// Compute proportional transfer amounts
fillResults.takerAssetFilledAmount = takerAssetFilledAmount;
- fillResults.makerAssetFilledAmount = getPartialAmount(
+ fillResults.makerAssetFilledAmount = getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount
);
- fillResults.makerFeePaid = getPartialAmount(
+ fillResults.makerFeePaid = getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerFee
);
- fillResults.takerFeePaid = getPartialAmount(
+ fillResults.takerFeePaid = getPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.takerFee
diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol
index 56b309a1b..20f4b1c33 100644
--- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol
+++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol
@@ -183,7 +183,7 @@ contract MixinMatchOrders is
leftTakerAssetFilledAmount = leftTakerAssetAmountRemaining;
// The right order receives an amount proportional to how much was spent.
- rightTakerAssetFilledAmount = getPartialAmount(
+ rightTakerAssetFilledAmount = getPartialAmountFloor(
rightOrder.takerAssetAmount,
rightOrder.makerAssetAmount,
leftTakerAssetFilledAmount
@@ -193,7 +193,7 @@ contract MixinMatchOrders is
rightTakerAssetFilledAmount = rightTakerAssetAmountRemaining;
// The left order receives an amount proportional to how much was spent.
- leftTakerAssetFilledAmount = getPartialAmount(
+ leftTakerAssetFilledAmount = getPartialAmountFloor(
rightOrder.makerAssetAmount,
rightOrder.takerAssetAmount,
rightTakerAssetFilledAmount
diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol
index 86194f461..1b77cfbcb 100644
--- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol
+++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol
@@ -298,7 +298,7 @@ contract MixinWrapperFunctions is
// Convert the remaining amount of makerAsset to buy into remaining amount
// of takerAsset to sell, assuming entire amount can be sold in the current order
- uint256 remainingTakerAssetFillAmount = getPartialAmount(
+ uint256 remainingTakerAssetFillAmount = getPartialAmountFloor(
orders[i].takerAssetAmount,
orders[i].makerAssetAmount,
remainingMakerAssetFillAmount
@@ -350,7 +350,7 @@ contract MixinWrapperFunctions is
// Convert the remaining amount of makerAsset to buy into remaining amount
// of takerAsset to sell, assuming entire amount can be sold in the current order
- uint256 remainingTakerAssetFillAmount = getPartialAmount(
+ uint256 remainingTakerAssetFillAmount = getPartialAmountFloor(
orders[i].takerAssetAmount,
orders[i].makerAssetAmount,
remainingMakerAssetFillAmount
diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol
index fa09da6ac..0e0fba5d2 100644
--- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol
+++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol
@@ -25,12 +25,12 @@ contract LibMath is
SafeMath
{
- /// @dev Calculates partial value given a numerator and denominator.
+ /// @dev Calculates partial value given a numerator and denominator rounded down.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
- /// @return Partial value of target.
- function getPartialAmount(
+ /// @return Partial value of target rounded down.
+ function getPartialAmountFloor(
uint256 numerator,
uint256 denominator,
uint256 target
@@ -39,19 +39,56 @@ contract LibMath is
pure
returns (uint256 partialAmount)
{
+ require(
+ denominator > 0,
+ "DIVISION_BY_ZERO"
+ );
+
partialAmount = safeDiv(
safeMul(numerator, target),
denominator
);
return partialAmount;
}
-
- /// @dev Checks if rounding error > 0.1%.
+
+ /// @dev Calculates partial value given a numerator and denominator rounded down.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target rounded up.
+ function getPartialAmountCeil(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target
+ )
+ internal
+ pure
+ returns (uint256 partialAmount)
+ {
+ require(
+ denominator > 0,
+ "DIVISION_BY_ZERO"
+ );
+
+ // safeDiv computes `floor(a / b)`. We use the identity (a, b integer):
+ // ceil(a / b) = floor((a + b - 1) / b)
+ // To implement `ceil(a / b)` using safeDiv.
+ partialAmount = safeDiv(
+ safeAdd(
+ safeMul(numerator, target),
+ safeSub(denominator, 1)
+ ),
+ denominator
+ );
+ return partialAmount;
+ }
+
+ /// @dev Checks if rounding error >= 0.1% when rounding down.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
- function isRoundingError(
+ function isRoundingErrorFloor(
uint256 numerator,
uint256 denominator,
uint256 target
@@ -60,16 +97,73 @@ contract LibMath is
pure
returns (bool isError)
{
- uint256 remainder = mulmod(target, numerator, denominator);
- if (remainder == 0) {
- return false; // No rounding error.
+ require(
+ denominator > 0,
+ "DIVISION_BY_ZERO"
+ );
+
+ // The absolute rounding error is the difference between the rounded
+ // value and the ideal value. The relative rounding error is the
+ // absolute rounding error divided by the absolute value of the
+ // ideal value. This is undefined when the ideal value is zero.
+ //
+ // The ideal value is `numerator * target / denominator`.
+ // Let's call `numerator * target % denominator` the remainder.
+ // The absolute error is `remainder / denominator`.
+ //
+ // When the ideal value is zero, we require the absolute error to
+ // be zero. Fortunately, this is always the case. The ideal value is
+ // zero iff `numerator == 0` and/or `target == 0`. In this case the
+ // remainder and absolute error are also zero.
+ if (target == 0 || numerator == 0) {
+ return false;
}
-
- uint256 errPercentageTimes1000000 = safeDiv(
- safeMul(remainder, 1000000),
- safeMul(numerator, target)
+
+ // Otherwise, we want the relative rounding error to be strictly
+ // less than 0.1%.
+ // The relative error is `remainder / (numerator * target)`.
+ // We want the relative error less than 1 / 1000:
+ // remainder / (numerator * denominator) < 1 / 1000
+ // or equivalently:
+ // 1000 * remainder < numerator * target
+ // so we have a rounding error iff:
+ // 1000 * remainder >= numerator * target
+ uint256 remainder = mulmod(target, numerator, denominator);
+ isError = safeMul(1000, remainder) >= safeMul(numerator, target);
+ return isError;
+ }
+
+ /// @dev Checks if rounding error >= 0.1% when rounding up.
+ /// @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
+ )
+ internal
+ pure
+ returns (bool isError)
+ {
+ require(
+ denominator > 0,
+ "DIVISION_BY_ZERO"
);
- isError = errPercentageTimes1000000 > 1000;
+
+ // See the comments in `isRoundingError`.
+ if (target == 0 || numerator == 0) {
+ // When either is zero, the ideal value and rounded value are zero
+ // and there is no rounding error. (Although the relative error
+ // is undefined.)
+ return false;
+ }
+ // Compute remainder as before
+ uint256 remainder = mulmod(target, numerator, denominator);
+ // TODO: safeMod
+ remainder = safeSub(denominator, remainder) % denominator;
+ isError = safeMul(1000, remainder) >= safeMul(numerator, target);
return isError;
}
}
diff --git a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol
index d9cec9edc..da9313e02 100644
--- a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol
+++ b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol
@@ -67,7 +67,7 @@ contract TestExchangeInternals is
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
- function publicGetPartialAmount(
+ function publicGetPartialAmountFloor(
uint256 numerator,
uint256 denominator,
uint256 target
@@ -76,15 +76,49 @@ contract TestExchangeInternals is
pure
returns (uint256 partialAmount)
{
- return getPartialAmount(numerator, denominator, target);
+ return getPartialAmountFloor(numerator, denominator, target);
}
- /// @dev Checks if rounding error > 0.1%.
+ /// @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 publicGetPartialAmountCeil(
+ 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 publicIsRoundingErrorFloor(
+ 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 publicIsRoundingError(
+ function publicIsRoundingErrorCeil(
uint256 numerator,
uint256 denominator,
uint256 target
@@ -93,7 +127,7 @@ contract TestExchangeInternals is
pure
returns (bool isError)
{
- return isRoundingError(numerator, denominator, target);
+ return isRoundingErrorCeil(numerator, denominator, target);
}
/// @dev Updates state with results of a fill order.
diff --git a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol
index 4a99dd9c1..c8c58545f 100644
--- a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol
+++ b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol
@@ -49,7 +49,7 @@ contract TestLibs is
return fillOrderCalldata;
}
- function publicGetPartialAmount(
+ function publicGetPartialAmountFloor(
uint256 numerator,
uint256 denominator,
uint256 target
@@ -58,7 +58,7 @@ contract TestLibs is
pure
returns (uint256 partialAmount)
{
- partialAmount = getPartialAmount(
+ partialAmount = getPartialAmountFloor(
numerator,
denominator,
target
@@ -66,7 +66,41 @@ contract TestLibs is
return partialAmount;
}
- function publicIsRoundingError(
+ function publicGetPartialAmountCeil(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target
+ )
+ public
+ pure
+ returns (uint256 partialAmount)
+ {
+ partialAmount = getPartialAmountCeil(
+ numerator,
+ denominator,
+ target
+ );
+ return partialAmount;
+ }
+
+ function publicIsRoundingErrorFloor(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target
+ )
+ public
+ pure
+ returns (bool isError)
+ {
+ isError = isRoundingErrorFloor(
+ numerator,
+ denominator,
+ target
+ );
+ return isError;
+ }
+
+ function publicIsRoundingErrorCeil(
uint256 numerator,
uint256 denominator,
uint256 target
@@ -75,7 +109,7 @@ contract TestLibs is
pure
returns (bool isError)
{
- isError = isRoundingError(
+ isError = isRoundingErrorCeil(
numerator,
denominator,
target