Skip to content

Commit c2594bf

Browse files
⚡️ Optimize gas consumption for mulDivUp and mulDivDown (#306)
Co-authored-by: t11s <greenfeatherhelp@gmail.com>
1 parent 352e1e9 commit c2594bf

2 files changed

Lines changed: 53 additions & 58 deletions

File tree

.gas-snapshot

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -146,22 +146,22 @@ ERC20Test:testTransfer() (gas: 60272)
146146
ERC20Test:testTransfer(address,uint256) (runs: 256, μ: 58773, ~: 60484)
147147
ERC20Test:testTransferFrom() (gas: 83777)
148148
ERC20Test:testTransferFrom(address,uint256,uint256) (runs: 256, μ: 86464, ~: 92841)
149-
ERC4626Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2881)
149+
ERC4626Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2885)
150150
ERC4626Test:testFailDepositWithNoApproval() (gas: 13357)
151151
ERC4626Test:testFailDepositWithNotEnoughApproval() (gas: 86993)
152152
ERC4626Test:testFailDepositZero() (gas: 7780)
153153
ERC4626Test:testFailMintWithNoApproval() (gas: 13296)
154154
ERC4626Test:testFailRedeemWithNoShareAmount() (gas: 32342)
155-
ERC4626Test:testFailRedeemWithNotEnoughShareAmount() (gas: 203638)
155+
ERC4626Test:testFailRedeemWithNotEnoughShareAmount() (gas: 203631)
156156
ERC4626Test:testFailRedeemZero() (gas: 7967)
157157
ERC4626Test:testFailWithdrawWithNoUnderlyingAmount() (gas: 32289)
158-
ERC4626Test:testFailWithdrawWithNotEnoughUnderlyingAmount() (gas: 203615)
159-
ERC4626Test:testMetadata(string,string) (runs: 256, μ: 1479572, ~: 1471277)
158+
ERC4626Test:testFailWithdrawWithNotEnoughUnderlyingAmount() (gas: 203595)
159+
ERC4626Test:testMetadata(string,string) (runs: 256, μ: 1489261, ~: 1481501)
160160
ERC4626Test:testMintZero() (gas: 54595)
161-
ERC4626Test:testMultipleMintDepositRedeemWithdraw() (gas: 411940)
162-
ERC4626Test:testSingleDepositWithdraw(uint128) (runs: 256, μ: 201569, ~: 201579)
163-
ERC4626Test:testSingleMintRedeem(uint128) (runs: 256, μ: 201484, ~: 201494)
164-
ERC4626Test:testVaultInteractionsForSomeoneElse() (gas: 286247)
161+
ERC4626Test:testMultipleMintDepositRedeemWithdraw() (gas: 411747)
162+
ERC4626Test:testSingleDepositWithdraw(uint128) (runs: 256, μ: 201526, ~: 201536)
163+
ERC4626Test:testSingleMintRedeem(uint128) (runs: 256, μ: 201451, ~: 201461)
164+
ERC4626Test:testVaultInteractionsForSomeoneElse() (gas: 286209)
165165
ERC4626Test:testWithdrawZero() (gas: 52462)
166166
ERC721Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2170)
167167
ERC721Test:testApprove() (gas: 78427)
@@ -240,44 +240,44 @@ ERC721Test:testTransferFromApproveAll() (gas: 92898)
240240
ERC721Test:testTransferFromApproveAll(uint256,address) (runs: 256, μ: 93182, ~: 93182)
241241
ERC721Test:testTransferFromSelf() (gas: 64776)
242242
ERC721Test:testTransferFromSelf(uint256,address) (runs: 256, μ: 65061, ~: 65061)
243-
FixedPointMathLibTest:testDifferentiallyFuzzSqrt(uint256) (runs: 256, μ: 13827, ~: 4181)
244-
FixedPointMathLibTest:testDivWadDown() (gas: 841)
245-
FixedPointMathLibTest:testDivWadDown(uint256,uint256) (runs: 256, μ: 718, ~: 820)
246-
FixedPointMathLibTest:testDivWadDownEdgeCases() (gas: 446)
247-
FixedPointMathLibTest:testDivWadUp() (gas: 1003)
248-
FixedPointMathLibTest:testDivWadUp(uint256,uint256) (runs: 256, μ: 809, ~: 972)
249-
FixedPointMathLibTest:testDivWadUpEdgeCases() (gas: 462)
250-
FixedPointMathLibTest:testFailDivWadDownOverflow(uint256,uint256) (runs: 256, μ: 443, ~: 419)
251-
FixedPointMathLibTest:testFailDivWadDownZeroDenominator() (gas: 342)
252-
FixedPointMathLibTest:testFailDivWadDownZeroDenominator(uint256) (runs: 256, μ: 397, ~: 397)
253-
FixedPointMathLibTest:testFailDivWadUpOverflow(uint256,uint256) (runs: 256, μ: 398, ~: 374)
254-
FixedPointMathLibTest:testFailDivWadUpZeroDenominator() (gas: 342)
255-
FixedPointMathLibTest:testFailDivWadUpZeroDenominator(uint256) (runs: 256, μ: 396, ~: 396)
256-
FixedPointMathLibTest:testFailMulDivDownOverflow(uint256,uint256,uint256) (runs: 256, μ: 437, ~: 414)
257-
FixedPointMathLibTest:testFailMulDivDownZeroDenominator() (gas: 338)
258-
FixedPointMathLibTest:testFailMulDivDownZeroDenominator(uint256,uint256) (runs: 256, μ: 395, ~: 395)
259-
FixedPointMathLibTest:testFailMulDivUpOverflow(uint256,uint256,uint256) (runs: 256, μ: 460, ~: 437)
260-
FixedPointMathLibTest:testFailMulDivUpZeroDenominator() (gas: 339)
261-
FixedPointMathLibTest:testFailMulDivUpZeroDenominator(uint256,uint256) (runs: 256, μ: 438, ~: 438)
262-
FixedPointMathLibTest:testFailMulWadDownOverflow(uint256,uint256) (runs: 256, μ: 424, ~: 387)
263-
FixedPointMathLibTest:testFailMulWadUpOverflow(uint256,uint256) (runs: 256, μ: 401, ~: 364)
264-
FixedPointMathLibTest:testMulDivDown() (gas: 1883)
265-
FixedPointMathLibTest:testMulDivDown(uint256,uint256,uint256) (runs: 256, μ: 691, ~: 793)
266-
FixedPointMathLibTest:testMulDivDownEdgeCases() (gas: 707)
267-
FixedPointMathLibTest:testMulDivUp() (gas: 2295)
268-
FixedPointMathLibTest:testMulDivUp(uint256,uint256,uint256) (runs: 256, μ: 835, ~: 1054)
269-
FixedPointMathLibTest:testMulDivUpEdgeCases() (gas: 845)
270-
FixedPointMathLibTest:testMulWadDown() (gas: 844)
271-
FixedPointMathLibTest:testMulWadDown(uint256,uint256) (runs: 256, μ: 686, ~: 810)
272-
FixedPointMathLibTest:testMulWadDownEdgeCases() (gas: 843)
273-
FixedPointMathLibTest:testMulWadUp() (gas: 981)
274-
FixedPointMathLibTest:testMulWadUp(uint256,uint256) (runs: 256, μ: 835, ~: 1073)
275-
FixedPointMathLibTest:testMulWadUpEdgeCases() (gas: 959)
243+
FixedPointMathLibTest:testDifferentiallyFuzzSqrt(uint256) (runs: 256, μ: 13830, ~: 4181)
244+
FixedPointMathLibTest:testDivWadDown() (gas: 820)
245+
FixedPointMathLibTest:testDivWadDown(uint256,uint256) (runs: 256, μ: 713, ~: 813)
246+
FixedPointMathLibTest:testDivWadDownEdgeCases() (gas: 439)
247+
FixedPointMathLibTest:testDivWadUp() (gas: 943)
248+
FixedPointMathLibTest:testDivWadUp(uint256,uint256) (runs: 256, μ: 795, ~: 952)
249+
FixedPointMathLibTest:testDivWadUpEdgeCases() (gas: 442)
250+
FixedPointMathLibTest:testFailDivWadDownOverflow(uint256,uint256) (runs: 256, μ: 441, ~: 419)
251+
FixedPointMathLibTest:testFailDivWadDownZeroDenominator() (gas: 332)
252+
FixedPointMathLibTest:testFailDivWadDownZeroDenominator(uint256) (runs: 256, μ: 387, ~: 387)
253+
FixedPointMathLibTest:testFailDivWadUpOverflow(uint256,uint256) (runs: 256, μ: 396, ~: 374)
254+
FixedPointMathLibTest:testFailDivWadUpZeroDenominator() (gas: 332)
255+
FixedPointMathLibTest:testFailDivWadUpZeroDenominator(uint256) (runs: 256, μ: 386, ~: 386)
256+
FixedPointMathLibTest:testFailMulDivDownOverflow(uint256,uint256,uint256) (runs: 256, μ: 434, ~: 414)
257+
FixedPointMathLibTest:testFailMulDivDownZeroDenominator() (gas: 328)
258+
FixedPointMathLibTest:testFailMulDivDownZeroDenominator(uint256,uint256) (runs: 256, μ: 385, ~: 385)
259+
FixedPointMathLibTest:testFailMulDivUpOverflow(uint256,uint256,uint256) (runs: 256, μ: 457, ~: 437)
260+
FixedPointMathLibTest:testFailMulDivUpZeroDenominator() (gas: 329)
261+
FixedPointMathLibTest:testFailMulDivUpZeroDenominator(uint256,uint256) (runs: 256, μ: 428, ~: 428)
262+
FixedPointMathLibTest:testFailMulWadDownOverflow(uint256,uint256) (runs: 256, μ: 421, ~: 387)
263+
FixedPointMathLibTest:testFailMulWadUpOverflow(uint256,uint256) (runs: 256, μ: 398, ~: 364)
264+
FixedPointMathLibTest:testMulDivDown() (gas: 1813)
265+
FixedPointMathLibTest:testMulDivDown(uint256,uint256,uint256) (runs: 256, μ: 686, ~: 786)
266+
FixedPointMathLibTest:testMulDivDownEdgeCases() (gas: 686)
267+
FixedPointMathLibTest:testMulDivUp() (gas: 2095)
268+
FixedPointMathLibTest:testMulDivUp(uint256,uint256,uint256) (runs: 256, μ: 821, ~: 1034)
269+
FixedPointMathLibTest:testMulDivUpEdgeCases() (gas: 785)
270+
FixedPointMathLibTest:testMulWadDown() (gas: 823)
271+
FixedPointMathLibTest:testMulWadDown(uint256,uint256) (runs: 256, μ: 681, ~: 803)
272+
FixedPointMathLibTest:testMulWadDownEdgeCases() (gas: 822)
273+
FixedPointMathLibTest:testMulWadUp() (gas: 921)
274+
FixedPointMathLibTest:testMulWadUp(uint256,uint256) (runs: 256, μ: 821, ~: 1053)
275+
FixedPointMathLibTest:testMulWadUpEdgeCases() (gas: 899)
276276
FixedPointMathLibTest:testRPow() (gas: 2164)
277277
FixedPointMathLibTest:testSqrt() (gas: 2580)
278278
FixedPointMathLibTest:testSqrt(uint256) (runs: 256, μ: 997, ~: 1013)
279279
FixedPointMathLibTest:testSqrtBack(uint256) (runs: 256, μ: 15210, ~: 340)
280-
FixedPointMathLibTest:testSqrtBackHashed(uint256) (runs: 256, μ: 59040, ~: 59500)
280+
FixedPointMathLibTest:testSqrtBackHashed(uint256) (runs: 256, μ: 59034, ~: 59500)
281281
FixedPointMathLibTest:testSqrtBackHashedSingle() (gas: 58937)
282282
LibStringTest:testDifferentiallyFuzzToString(uint256,bytes) (runs: 256, μ: 20749, ~: 8925)
283283
LibStringTest:testToString() (gas: 10047)

src/utils/FixedPointMathLib.sol

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ library FixedPointMathLib {
99
SIMPLIFIED FIXED POINT OPERATIONS
1010
//////////////////////////////////////////////////////////////*/
1111

12+
uint256 internal constant MAX_UINT256 = 2**256 - 1;
13+
1214
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
1315

1416
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
@@ -37,16 +39,13 @@ library FixedPointMathLib {
3739
uint256 denominator
3840
) internal pure returns (uint256 z) {
3941
assembly {
40-
// Store x * y in z for now.
41-
z := mul(x, y)
42-
43-
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
44-
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
42+
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
43+
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
4544
revert(0, 0)
4645
}
4746

48-
// Divide z by the denominator.
49-
z := div(z, denominator)
47+
// Divide x * y by the denominator.
48+
z := div(mul(x, y), denominator)
5049
}
5150
}
5251

@@ -56,18 +55,14 @@ library FixedPointMathLib {
5655
uint256 denominator
5756
) internal pure returns (uint256 z) {
5857
assembly {
59-
// Store x * y in z for now.
60-
z := mul(x, y)
61-
62-
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
63-
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
58+
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
59+
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
6460
revert(0, 0)
6561
}
6662

67-
// First, divide z - 1 by the denominator and add 1.
68-
// We allow z - 1 to underflow if z is 0, because we multiply the
69-
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
70-
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
63+
// If x * y modulo the denominator is strictly greater than 0,
64+
// 1 is added to round up the division of x * y by the denominator.
65+
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
7166
}
7267
}
7368

0 commit comments

Comments
 (0)