From eca31f225db0888e27c8d6a6a28471439f954921 Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Fri, 4 Aug 2023 11:55:06 -0400 Subject: [PATCH 1/9] feat: compound interest --- src/Blue.sol | 2 +- src/libraries/FixedPointMathLib.sol | 20 ++++++++++++++++ test/forge/Math.t.sol | 37 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 test/forge/Math.t.sol diff --git a/src/Blue.sol b/src/Blue.sol index beac350a9..d1b7783f8 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -375,7 +375,7 @@ contract Blue is IBlue { if (marketTotalBorrow != 0) { uint256 borrowRate = IIrm(market.irm).borrowRate(market); - uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate * elapsed); + uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate.wTaylorCompounded(elapsed)); totalBorrow[id] = marketTotalBorrow + accruedInterests; totalSupply[id] += accruedInterests; diff --git a/src/libraries/FixedPointMathLib.sol b/src/libraries/FixedPointMathLib.sol index 8afddd034..52140e15e 100644 --- a/src/libraries/FixedPointMathLib.sol +++ b/src/libraries/FixedPointMathLib.sol @@ -29,6 +29,15 @@ library FixedPointMathLib { return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. } + /// @dev The sum of the last three terms in a four term taylor series expansion + /// to approximate a compound interest rate: (1 + x)^n - 1. + function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { + uint256 firstTerm = x * n; + uint256 secondTerm = mulWadDown(firstTerm, x * zeroFloorSub(n, 1)) / 2; + uint256 thirdTerm = mulWadDown(secondTerm, x * zeroFloorSub(n, 2)) / 3; + return firstTerm + secondTerm + thirdTerm; + } + /*////////////////////////////////////////////////////////////// LOW LEVEL FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ @@ -55,4 +64,15 @@ library FixedPointMathLib { z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) } } + + /*////////////////////////////////////////////////////////////// + INTEGER OPERATIONS + //////////////////////////////////////////////////////////////*/ + + /// @dev Returns max(x - y, 0). + function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := mul(gt(x, y), sub(x, y)) + } + } } diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol new file mode 100644 index 000000000..b53fd285e --- /dev/null +++ b/test/forge/Math.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "src/libraries/FixedPointMathLib.sol"; + +contract MathTest is Test { + using FixedPointMathLib for uint256; + + function testTaylorSeriesExpansion(uint256 rate, uint256 timeElapsed) public { + // Assume rate is less than a ~500% APY. (~180% APR) + vm.assume(rate < (FixedPointMathLib.WAD / 20_000_000) && timeElapsed < 365 days); + uint256 result = rate.wTaylorCompounded(timeElapsed) + FixedPointMathLib.WAD; + uint256 toCompare = wPow(FixedPointMathLib.WAD + rate, timeElapsed); + assertLe(result, toCompare, "rate should be less than the compounded rate"); + assertGe( + result, + FixedPointMathLib.WAD + timeElapsed * rate / FixedPointMathLib.WAD, + "rate should be greater than the simple interest rate" + ); + assertLe((toCompare - result) * 100_00 / toCompare, 2_00, "The error should be less than or equal to 2%"); + } + + // Exponentiation by squaring with rounding up. + function wPow(uint256 x, uint256 n) private pure returns (uint256 z) { + z = n % 2 != 0 ? x : FixedPointMathLib.WAD; + + for (n /= 2; n != 0; n /= 2) { + x = x.mulWadUp(x); + + if (n % 2 != 0) { + z = z.mulWadUp(x); + } + } + } +} \ No newline at end of file From 54bbd92a17539992a180eeb0df90b91b4c06abd5 Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Fri, 4 Aug 2023 12:02:56 -0400 Subject: [PATCH 2/9] Update test/forge/Math.t.sol Co-authored-by: Romain Milon Signed-off-by: Patrick Kim --- test/forge/Math.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol index b53fd285e..246069abd 100644 --- a/test/forge/Math.t.sol +++ b/test/forge/Math.t.sol @@ -8,7 +8,8 @@ import "src/libraries/FixedPointMathLib.sol"; contract MathTest is Test { using FixedPointMathLib for uint256; - function testTaylorSeriesExpansion(uint256 rate, uint256 timeElapsed) public { + function testWTaylorCompounded(uint256 rate, uint256 timeElapsed) public { + // Assume rate is less than a ~500% APY. (~180% APR) vm.assume(rate < (FixedPointMathLib.WAD / 20_000_000) && timeElapsed < 365 days); uint256 result = rate.wTaylorCompounded(timeElapsed) + FixedPointMathLib.WAD; From 4f0aacfbb7bbc3f2f7a148bd977f5caa28d65b5b Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Fri, 4 Aug 2023 12:31:23 -0400 Subject: [PATCH 3/9] test: increase tolerance --- test/forge/Math.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol index b53fd285e..1b50f1ab3 100644 --- a/test/forge/Math.t.sol +++ b/test/forge/Math.t.sol @@ -19,7 +19,7 @@ contract MathTest is Test { FixedPointMathLib.WAD + timeElapsed * rate / FixedPointMathLib.WAD, "rate should be greater than the simple interest rate" ); - assertLe((toCompare - result) * 100_00 / toCompare, 2_00, "The error should be less than or equal to 2%"); + assertLe((toCompare - result) * 100_00 / toCompare, 8_00, "The error should be less than or equal to 8%"); } // Exponentiation by squaring with rounding up. From a464d940a248e6367662df3b715cea2808a85fbb Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Sat, 5 Aug 2023 13:40:50 -0400 Subject: [PATCH 4/9] Update test/forge/Math.t.sol Co-authored-by: makcandrov Signed-off-by: Patrick Kim --- test/forge/Math.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol index 973e2d9ff..2d6987879 100644 --- a/test/forge/Math.t.sol +++ b/test/forge/Math.t.sol @@ -35,4 +35,4 @@ contract MathTest is Test { } } } -} \ No newline at end of file +} From 5f0a829e1fed271a171b9a3ce7a2bc169533ff9a Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Wed, 9 Aug 2023 14:29:40 +0200 Subject: [PATCH 5/9] test: fix comparison and implement suggestion --- test/forge/Math.t.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol index 2d6987879..5c1ef6479 100644 --- a/test/forge/Math.t.sol +++ b/test/forge/Math.t.sol @@ -17,7 +17,7 @@ contract MathTest is Test { assertLe(result, toCompare, "rate should be less than the compounded rate"); assertGe( result, - FixedPointMathLib.WAD + timeElapsed * rate / FixedPointMathLib.WAD, + FixedPointMathLib.WAD + timeElapsed * rate, "rate should be greater than the simple interest rate" ); assertLe((toCompare - result) * 100_00 / toCompare, 8_00, "The error should be less than or equal to 8%"); @@ -25,14 +25,10 @@ contract MathTest is Test { // Exponentiation by squaring with rounding up. function wPow(uint256 x, uint256 n) private pure returns (uint256 z) { - z = n % 2 != 0 ? x : FixedPointMathLib.WAD; - - for (n /= 2; n != 0; n /= 2) { + z = FixedPointMathLib.WAD; + for (; n != 0; n /= 2) { + z = n % 2 != 0 ? z.mulWadUp(x) : z; x = x.mulWadUp(x); - - if (n % 2 != 0) { - z = z.mulWadUp(x); - } } } } From 4c7997cb04e2d425b9bdf97ba7cc66ae7085aed3 Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Wed, 9 Aug 2023 14:34:34 +0200 Subject: [PATCH 6/9] test: format --- test/forge/Math.t.sol | 65 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/test/forge/Math.t.sol b/test/forge/Math.t.sol index 5c1ef6479..29422dc63 100644 --- a/test/forge/Math.t.sol +++ b/test/forge/Math.t.sol @@ -1,34 +1,31 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -import "src/libraries/FixedPointMathLib.sol"; - -contract MathTest is Test { - using FixedPointMathLib for uint256; - - function testWTaylorCompounded(uint256 rate, uint256 timeElapsed) public { - - // Assume rate is less than a ~500% APY. (~180% APR) - vm.assume(rate < (FixedPointMathLib.WAD / 20_000_000) && timeElapsed < 365 days); - uint256 result = rate.wTaylorCompounded(timeElapsed) + FixedPointMathLib.WAD; - uint256 toCompare = wPow(FixedPointMathLib.WAD + rate, timeElapsed); - assertLe(result, toCompare, "rate should be less than the compounded rate"); - assertGe( - result, - FixedPointMathLib.WAD + timeElapsed * rate, - "rate should be greater than the simple interest rate" - ); - assertLe((toCompare - result) * 100_00 / toCompare, 8_00, "The error should be less than or equal to 8%"); - } - - // Exponentiation by squaring with rounding up. - function wPow(uint256 x, uint256 n) private pure returns (uint256 z) { - z = FixedPointMathLib.WAD; - for (; n != 0; n /= 2) { - z = n % 2 != 0 ? z.mulWadUp(x) : z; - x = x.mulWadUp(x); - } - } -} +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "src/libraries/FixedPointMathLib.sol"; + +contract MathTest is Test { + using FixedPointMathLib for uint256; + + function testWTaylorCompounded(uint256 rate, uint256 timeElapsed) public { + // Assume rate is less than a ~500% APY. (~180% APR) + vm.assume(rate < (FixedPointMathLib.WAD / 20_000_000) && timeElapsed < 365 days); + uint256 result = rate.wTaylorCompounded(timeElapsed) + FixedPointMathLib.WAD; + uint256 toCompare = wPow(FixedPointMathLib.WAD + rate, timeElapsed); + assertLe(result, toCompare, "rate should be less than the compounded rate"); + assertGe( + result, FixedPointMathLib.WAD + timeElapsed * rate, "rate should be greater than the simple interest rate" + ); + assertLe((toCompare - result) * 100_00 / toCompare, 8_00, "The error should be less than or equal to 8%"); + } + + // Exponentiation by squaring with rounding up. + function wPow(uint256 x, uint256 n) private pure returns (uint256 z) { + z = FixedPointMathLib.WAD; + for (; n != 0; n /= 2) { + z = n % 2 != 0 ? z.mulWadUp(x) : z; + x = x.mulWadUp(x); + } + } +} From 1e28b1cd83aa5766230729d610b52311e5d18fe2 Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Wed, 9 Aug 2023 14:38:12 +0200 Subject: [PATCH 7/9] refactor: use suggested --- src/libraries/FixedPointMathLib.sol | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/libraries/FixedPointMathLib.sol b/src/libraries/FixedPointMathLib.sol index 52140e15e..621907316 100644 --- a/src/libraries/FixedPointMathLib.sol +++ b/src/libraries/FixedPointMathLib.sol @@ -32,9 +32,18 @@ library FixedPointMathLib { /// @dev The sum of the last three terms in a four term taylor series expansion /// to approximate a compound interest rate: (1 + x)^n - 1. function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { - uint256 firstTerm = x * n; - uint256 secondTerm = mulWadDown(firstTerm, x * zeroFloorSub(n, 1)) / 2; - uint256 thirdTerm = mulWadDown(secondTerm, x * zeroFloorSub(n, 2)) / 3; + uint256 mon = x; + uint256 coeff = n; + uint256 firstTerm = mon * coeff; + + mon = mulWadDown(mon, x); + coeff = (coeff * zeroFloorSub(n, 1)) / 2; + uint256 secondTerm = mon * coeff; + + mon = mulWadDown(mon, x); + coeff = (coeff * zeroFloorSub(n, 2)) / 3; + uint256 thirdTerm = mon * coeff; + return firstTerm + secondTerm + thirdTerm; } From 2604061f7a6852c6d220092e7a76dba1789cec4e Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Wed, 9 Aug 2023 15:07:54 +0200 Subject: [PATCH 8/9] refactor: make terms clearer --- src/libraries/FixedPointMathLib.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/FixedPointMathLib.sol b/src/libraries/FixedPointMathLib.sol index 621907316..e4c033e6f 100644 --- a/src/libraries/FixedPointMathLib.sol +++ b/src/libraries/FixedPointMathLib.sol @@ -32,17 +32,17 @@ library FixedPointMathLib { /// @dev The sum of the last three terms in a four term taylor series expansion /// to approximate a compound interest rate: (1 + x)^n - 1. function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { - uint256 mon = x; + uint256 seriesTerm = x; uint256 coeff = n; - uint256 firstTerm = mon * coeff; + uint256 firstTerm = coeff * seriesTerm; - mon = mulWadDown(mon, x); + seriesTerm = mulWadDown(seriesTerm, x); coeff = (coeff * zeroFloorSub(n, 1)) / 2; - uint256 secondTerm = mon * coeff; + uint256 secondTerm = coeff * seriesTerm; - mon = mulWadDown(mon, x); + seriesTerm = mulWadDown(seriesTerm, x); coeff = (coeff * zeroFloorSub(n, 2)) / 3; - uint256 thirdTerm = mon * coeff; + uint256 thirdTerm = coeff * seriesTerm; return firstTerm + secondTerm + thirdTerm; } From 1e7452267fbd384ab3ecf49a3d18591be61a3db3 Mon Sep 17 00:00:00 2001 From: Patrick Kim Date: Wed, 9 Aug 2023 15:47:01 +0200 Subject: [PATCH 9/9] refactor: revert compounding change --- src/libraries/FixedPointMathLib.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libraries/FixedPointMathLib.sol b/src/libraries/FixedPointMathLib.sol index e4c033e6f..c8623c040 100644 --- a/src/libraries/FixedPointMathLib.sol +++ b/src/libraries/FixedPointMathLib.sol @@ -32,17 +32,9 @@ library FixedPointMathLib { /// @dev The sum of the last three terms in a four term taylor series expansion /// to approximate a compound interest rate: (1 + x)^n - 1. function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { - uint256 seriesTerm = x; - uint256 coeff = n; - uint256 firstTerm = coeff * seriesTerm; - - seriesTerm = mulWadDown(seriesTerm, x); - coeff = (coeff * zeroFloorSub(n, 1)) / 2; - uint256 secondTerm = coeff * seriesTerm; - - seriesTerm = mulWadDown(seriesTerm, x); - coeff = (coeff * zeroFloorSub(n, 2)) / 3; - uint256 thirdTerm = coeff * seriesTerm; + uint256 firstTerm = x * n; + uint256 secondTerm = mulWadDown(firstTerm, x * zeroFloorSub(n, 1)) / 2; + uint256 thirdTerm = mulWadDown(secondTerm, x * zeroFloorSub(n, 2)) / 3; return firstTerm + secondTerm + thirdTerm; }