From 0ecb96a37303c8a57d414e7f515ed9f1058fc6d9 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 2 Aug 2022 14:00:11 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20Add=20percent=20avg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/math/PercentageMath.sol | 20 ++++++++++++++ test/TestPercentageMath.sol | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/math/PercentageMath.sol b/src/math/PercentageMath.sol index 49a9bfb..a28b42f 100644 --- a/src/math/PercentageMath.sol +++ b/src/math/PercentageMath.sol @@ -13,6 +13,11 @@ library PercentageMath { uint256 internal constant MAX_UINT256 = 2**256 - 1; uint256 internal constant MAX_UINT256_MINUS_HALF_PERCENTAGE = 2**256 - 1 - 0.5e4; + /// ERRORS /// + + // Thrown when percentage is above 100%. + error PercentageTooHigh(); + /// INTERNAL /// /// @notice Executes a percentage multiplication. @@ -51,4 +56,19 @@ library PercentageMath { y := div(add(mul(x, PERCENTAGE_FACTOR), y), percentage) } } + + /// @notice Executes a percent average, given an interval [x, y] and a percent p: x * (1 - p) + y * p + /// @param x The value the start of the interval (included). + /// @param y The value the end of the interval (included). + /// @param percentage The percentage of the interval to be calculated. + /// @return the average of x and y, weighted by percentage. + function percentAvg( + uint256 x, + uint256 y, + uint256 percentage + ) internal pure returns (uint256) { + if (percentage > PercentageMath.PERCENTAGE_FACTOR) revert PercentageTooHigh(); + + return percentMul(x, PercentageMath.PERCENTAGE_FACTOR - percentage) + percentMul(y, percentage); + } } diff --git a/test/TestPercentageMath.sol b/test/TestPercentageMath.sol index 46783d3..6afa9a3 100644 --- a/test/TestPercentageMath.sol +++ b/test/TestPercentageMath.sol @@ -14,6 +14,14 @@ contract PercentageMathFunctions { function percentDiv(uint256 x, uint256 y) public pure returns (uint256) { return PercentageMath.percentDiv(x, y); } + + function percentAvg( + uint256 x, + uint256 y, + uint256 percentage + ) public pure returns (uint256) { + return PercentageMath.percentAvg(x, y, percentage); + } } contract PercentageMathFunctionsRef { @@ -24,6 +32,16 @@ contract PercentageMathFunctionsRef { function percentDiv(uint256 x, uint256 y) public pure returns (uint256) { return PercentageMathRef.percentDiv(x, y); } + + function percentAvg( + uint256 x, + uint256 y, + uint256 percentage + ) public pure returns (uint256) { + return + PercentageMathRef.percentMul(x, PercentageMathRef.PERCENTAGE_FACTOR - percentage) + + PercentageMathRef.percentMul(y, percentage); + } } contract TestPercentageMath is Test { @@ -73,6 +91,36 @@ contract TestPercentageMath is Test { vm.expectRevert(); PercentageMath.percentDiv(x, y); + + function testPercentAvg( + uint256 x, + uint256 y, + uint16 percentage + ) public { + vm.assume(percentage <= PERCENTAGE_FACTOR); + + vm.assume( + percentage < PercentageMathRef.PERCENTAGE_FACTOR && + x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PercentageMathRef.PERCENTAGE_FACTOR - percentage) + ); + vm.assume(percentage > 0 && y <= MAX_UINT256_MINUS_HALF_PERCENTAGE / percentage); + + assertEq( + PercentageMath.percentAvg(x, y, percentage), + PercentageMathRef.percentMul(x, PercentageMathRef.PERCENTAGE_FACTOR - percentage) + + PercentageMathRef.percentMul(y, percentage) + ); + } + + function testPercentAvgRevertWhenPercentageTooHigh( + uint256 x, + uint256 y, + uint256 percentage + ) public { + vm.assume(percentage > PERCENTAGE_FACTOR); + + vm.expectRevert(abi.encodeWithSignature("PercentageTooHigh()")); + PercentageMath.percentAvg(x, y, percentage); } /// GAS COMPARISONS /// @@ -86,4 +134,9 @@ contract TestPercentageMath is Test { math.percentDiv(1 ether, 1_000); mathRef.percentDiv(1 ether, 1_000); } + + function testGasPercentageAvg() public view { + math.percentAvg(1 ether, 2 ether, 5_000); + mathRef.percentAvg(1 ether, 2 ether, 5_000); + } } From 55bbbb04c4fa14f10711dee0a5c59bf4d9f8c20f Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 1 Aug 2022 15:27:13 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=90=9B=20Copy=20revert=20to=20referen?= =?UTF-8?q?ce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/math/PercentageMath.sol | 4 ++-- test/TestPercentageMath.sol | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/math/PercentageMath.sol b/src/math/PercentageMath.sol index a28b42f..cff7363 100644 --- a/src/math/PercentageMath.sol +++ b/src/math/PercentageMath.sol @@ -58,8 +58,8 @@ library PercentageMath { } /// @notice Executes a percent average, given an interval [x, y] and a percent p: x * (1 - p) + y * p - /// @param x The value the start of the interval (included). - /// @param y The value the end of the interval (included). + /// @param x The value at the start of the interval (included). + /// @param y The value at the end of the interval (included). /// @param percentage The percentage of the interval to be calculated. /// @return the average of x and y, weighted by percentage. function percentAvg( diff --git a/test/TestPercentageMath.sol b/test/TestPercentageMath.sol index 6afa9a3..662142a 100644 --- a/test/TestPercentageMath.sol +++ b/test/TestPercentageMath.sol @@ -25,6 +25,8 @@ contract PercentageMathFunctions { } contract PercentageMathFunctionsRef { + error PercentageTooHigh(); + function percentMul(uint256 x, uint256 y) public pure returns (uint256) { return PercentageMathRef.percentMul(x, y); } @@ -38,6 +40,8 @@ contract PercentageMathFunctionsRef { uint256 y, uint256 percentage ) public pure returns (uint256) { + if (percentage > PercentageMath.PERCENTAGE_FACTOR) revert PercentageTooHigh(); + return PercentageMathRef.percentMul(x, PercentageMathRef.PERCENTAGE_FACTOR - percentage) + PercentageMathRef.percentMul(y, percentage); @@ -98,7 +102,6 @@ contract TestPercentageMath is Test { uint16 percentage ) public { vm.assume(percentage <= PERCENTAGE_FACTOR); - vm.assume( percentage < PercentageMathRef.PERCENTAGE_FACTOR && x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PercentageMathRef.PERCENTAGE_FACTOR - percentage) From cfc3e47acd5afac7e97f98260c10090138b1f7a1 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 1 Aug 2022 15:31:30 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=8E=A8=20Access=20constant=20directly?= =?UTF-8?q?=20instead=20of=20via=20getter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/math/PercentageMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/math/PercentageMath.sol b/src/math/PercentageMath.sol index cff7363..105481d 100644 --- a/src/math/PercentageMath.sol +++ b/src/math/PercentageMath.sol @@ -67,8 +67,8 @@ library PercentageMath { uint256 y, uint256 percentage ) internal pure returns (uint256) { - if (percentage > PercentageMath.PERCENTAGE_FACTOR) revert PercentageTooHigh(); + if (percentage > PERCENTAGE_FACTOR) revert PercentageTooHigh(); - return percentMul(x, PercentageMath.PERCENTAGE_FACTOR - percentage) + percentMul(y, percentage); + return percentMul(x, PERCENTAGE_FACTOR - percentage) + percentMul(y, percentage); } } From 2fe780aac4df0af3734209eeac7c1ced7337fa16 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 1 Aug 2022 16:03:39 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20naming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/math/PercentageMath.sol | 4 ++-- test/TestPercentageMath.sol | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/math/PercentageMath.sol b/src/math/PercentageMath.sol index 105481d..9be3837 100644 --- a/src/math/PercentageMath.sol +++ b/src/math/PercentageMath.sol @@ -57,12 +57,12 @@ library PercentageMath { } } - /// @notice Executes a percent average, given an interval [x, y] and a percent p: x * (1 - p) + y * p + /// @notice Executes a weighted average, given an interval [x, y] and a percent p: x * (1 - p) + y * p /// @param x The value at the start of the interval (included). /// @param y The value at the end of the interval (included). /// @param percentage The percentage of the interval to be calculated. /// @return the average of x and y, weighted by percentage. - function percentAvg( + function weightedAvg( uint256 x, uint256 y, uint256 percentage diff --git a/test/TestPercentageMath.sol b/test/TestPercentageMath.sol index 662142a..d9705ac 100644 --- a/test/TestPercentageMath.sol +++ b/test/TestPercentageMath.sol @@ -103,14 +103,13 @@ contract TestPercentageMath is Test { ) public { vm.assume(percentage <= PERCENTAGE_FACTOR); vm.assume( - percentage < PercentageMathRef.PERCENTAGE_FACTOR && - x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PercentageMathRef.PERCENTAGE_FACTOR - percentage) + percentage < PERCENTAGE_FACTOR && x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PERCENTAGE_FACTOR - percentage) ); vm.assume(percentage > 0 && y <= MAX_UINT256_MINUS_HALF_PERCENTAGE / percentage); assertEq( PercentageMath.percentAvg(x, y, percentage), - PercentageMathRef.percentMul(x, PercentageMathRef.PERCENTAGE_FACTOR - percentage) + + PercentageMathRef.percentMul(x, PERCENTAGE_FACTOR - percentage) + PercentageMathRef.percentMul(y, percentage) ); } From ca425748a6cb99a9a9901e9fff26c78e44e6d7a1 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 2 Aug 2022 14:00:28 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=85=20Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/TestPercentageMath.sol | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/TestPercentageMath.sol b/test/TestPercentageMath.sol index d9705ac..f683d32 100644 --- a/test/TestPercentageMath.sol +++ b/test/TestPercentageMath.sol @@ -15,12 +15,12 @@ contract PercentageMathFunctions { return PercentageMath.percentDiv(x, y); } - function percentAvg( + function weightedAvg( uint256 x, uint256 y, uint256 percentage ) public pure returns (uint256) { - return PercentageMath.percentAvg(x, y, percentage); + return PercentageMath.weightedAvg(x, y, percentage); } } @@ -35,7 +35,7 @@ contract PercentageMathFunctionsRef { return PercentageMathRef.percentDiv(x, y); } - function percentAvg( + function weightedAvg( uint256 x, uint256 y, uint256 percentage @@ -95,26 +95,26 @@ contract TestPercentageMath is Test { vm.expectRevert(); PercentageMath.percentDiv(x, y); + } - function testPercentAvg( + function testWeightedAvg( uint256 x, uint256 y, uint16 percentage ) public { vm.assume(percentage <= PERCENTAGE_FACTOR); - vm.assume( - percentage < PERCENTAGE_FACTOR && x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PERCENTAGE_FACTOR - percentage) - ); - vm.assume(percentage > 0 && y <= MAX_UINT256_MINUS_HALF_PERCENTAGE / percentage); + if (percentage > 0) vm.assume(y <= MAX_UINT256_MINUS_HALF_PERCENTAGE / percentage); + if (percentage < PERCENTAGE_FACTOR) + vm.assume(x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PERCENTAGE_FACTOR - percentage)); assertEq( - PercentageMath.percentAvg(x, y, percentage), + PercentageMath.weightedAvg(x, y, percentage), PercentageMathRef.percentMul(x, PERCENTAGE_FACTOR - percentage) + PercentageMathRef.percentMul(y, percentage) ); } - function testPercentAvgRevertWhenPercentageTooHigh( + function testWeightedAvgRevertWhenPercentageTooHigh( uint256 x, uint256 y, uint256 percentage @@ -122,7 +122,7 @@ contract TestPercentageMath is Test { vm.assume(percentage > PERCENTAGE_FACTOR); vm.expectRevert(abi.encodeWithSignature("PercentageTooHigh()")); - PercentageMath.percentAvg(x, y, percentage); + PercentageMath.weightedAvg(x, y, percentage); } /// GAS COMPARISONS /// @@ -138,7 +138,7 @@ contract TestPercentageMath is Test { } function testGasPercentageAvg() public view { - math.percentAvg(1 ether, 2 ether, 5_000); - mathRef.percentAvg(1 ether, 2 ether, 5_000); + math.weightedAvg(1 ether, 2 ether, 5_000); + mathRef.weightedAvg(1 ether, 2 ether, 5_000); } } From 6fa9ea74b4736174e8c9dc1f7cd84537e51a674c Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 2 Aug 2022 14:03:05 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=8E=A8=20Use=20contract=20ref=20inste?= =?UTF-8?q?ad=20of=20rewriting=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/TestPercentageMath.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/TestPercentageMath.sol b/test/TestPercentageMath.sol index f683d32..59a9fc3 100644 --- a/test/TestPercentageMath.sol +++ b/test/TestPercentageMath.sol @@ -107,11 +107,7 @@ contract TestPercentageMath is Test { if (percentage < PERCENTAGE_FACTOR) vm.assume(x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PERCENTAGE_FACTOR - percentage)); - assertEq( - PercentageMath.weightedAvg(x, y, percentage), - PercentageMathRef.percentMul(x, PERCENTAGE_FACTOR - percentage) + - PercentageMathRef.percentMul(y, percentage) - ); + assertEq(PercentageMath.weightedAvg(x, y, percentage), mathRef.weightedAvg(x, y, percentage)); } function testWeightedAvgRevertWhenPercentageTooHigh(