Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: continuous compounding #254

Merged
merged 6 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions src/libraries/FixedPointMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ 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.
/// to approximate a continuous compound interest rate: e^(nx) - 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 secondTerm = mulWadDown(firstTerm, firstTerm) / 2;
uint256 thirdTerm = mulWadDown(secondTerm, firstTerm) / 3;

return firstTerm + secondTerm + thirdTerm;
}
Expand Down Expand Up @@ -65,15 +65,4 @@ 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))
}
}
}
15 changes: 4 additions & 11 deletions test/forge/Math.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,21 @@ pragma solidity ^0.8.0;
import "forge-std/Test.sol";

import "src/libraries/FixedPointMathLib.sol";
import "test/forge/helpers/WadMath.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)
peyha marked this conversation as resolved.
Show resolved Hide resolved
vm.assume(rate < (FixedPointMathLib.WAD / 20_000_000) && timeElapsed < 365 days);
rate = bound(rate, 0, FixedPointMathLib.WAD / 20_000_000);
timeElapsed = bound(timeElapsed, 0, 365 days);
uint256 result = rate.wTaylorCompounded(timeElapsed) + FixedPointMathLib.WAD;
uint256 toCompare = wPow(FixedPointMathLib.WAD + rate, timeElapsed);
uint256 toCompare = WadMath.wadExpUp(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);
}
}
}
166 changes: 166 additions & 0 deletions test/forge/helpers/WadMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

/**
* @title Fixed point arithmetic library
* @author Alberto Cuesta Cañada, Jacob Eliosoff, Alex Roan
* @notice This library is imported for testing purposes only. It is not used in production.
* Code is cut from https://raw.githubusercontent.com/usmfum/USM/master/contracts/WadMath.sol
*/
library WadMath {
uint256 public constant WAD = 1e18;
uint256 public constant FLOOR_LOG_2_WAD_SCALED = 158961593653514369813532673448321674075; // log2(1e18) * 2**121
uint256 public constant CEIL_LOG_2_E_SCALED_OVER_WAD = 3835341275459348170; // log2(e) * 2**121 / 1e18

function wadDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x * WAD + y; // 101 (1.01) / 1000 (10) -> (101 * 100 + 1000 - 1) / 1000 -> 11 (0.11 = 0.101 rounded up).
unchecked {
z -= 1;
} // Can do unchecked subtraction since division in next line will catch y = 0 case anyway
z /= y;
}

function wadExpUp(uint256 y) internal pure returns (uint256 z) {
uint256 exponent = FLOOR_LOG_2_WAD_SCALED - CEIL_LOG_2_E_SCALED_OVER_WAD * y;
require(exponent <= type(uint128).max, "exponent overflow");
uint256 wadOneOverExpY = pow_2(uint128(exponent));
z = wadDivUp(WAD, wadOneOverExpY);
}

/**
* Calculate 2 raised into given power.
*
* @param x power to raise 2 into, multiplied by 2^121
* @return z 2 raised into given power
*/
function pow_2(uint128 x) internal pure returns (uint128 z) {
unchecked {
uint256 r = 0x80000000000000000000000000000000;
if (x & 0x1000000000000000000000000000000 > 0) r = r * 0xb504f333f9de6484597d89b3754abe9f >> 127;
if (x & 0x800000000000000000000000000000 > 0) r = r * 0x9837f0518db8a96f46ad23182e42f6f6 >> 127;
if (x & 0x400000000000000000000000000000 > 0) r = r * 0x8b95c1e3ea8bd6e6fbe4628758a53c90 >> 127;
if (x & 0x200000000000000000000000000000 > 0) r = r * 0x85aac367cc487b14c5c95b8c2154c1b2 >> 127;
if (x & 0x100000000000000000000000000000 > 0) r = r * 0x82cd8698ac2ba1d73e2a475b46520bff >> 127;
if (x & 0x80000000000000000000000000000 > 0) r = r * 0x8164d1f3bc0307737be56527bd14def4 >> 127;
if (x & 0x40000000000000000000000000000 > 0) r = r * 0x80b1ed4fd999ab6c25335719b6e6fd20 >> 127;
if (x & 0x20000000000000000000000000000 > 0) r = r * 0x8058d7d2d5e5f6b094d589f608ee4aa2 >> 127;
if (x & 0x10000000000000000000000000000 > 0) r = r * 0x802c6436d0e04f50ff8ce94a6797b3ce >> 127;
if (x & 0x8000000000000000000000000000 > 0) r = r * 0x8016302f174676283690dfe44d11d008 >> 127;
if (x & 0x4000000000000000000000000000 > 0) r = r * 0x800b179c82028fd0945e54e2ae18f2f0 >> 127;
if (x & 0x2000000000000000000000000000 > 0) r = r * 0x80058baf7fee3b5d1c718b38e549cb93 >> 127;
if (x & 0x1000000000000000000000000000 > 0) r = r * 0x8002c5d00fdcfcb6b6566a58c048be1f >> 127;
if (x & 0x800000000000000000000000000 > 0) r = r * 0x800162e61bed4a48e84c2e1a463473d9 >> 127;
if (x & 0x400000000000000000000000000 > 0) r = r * 0x8000b17292f702a3aa22beacca949013 >> 127;
if (x & 0x200000000000000000000000000 > 0) r = r * 0x800058b92abbae02030c5fa5256f41fe >> 127;
if (x & 0x100000000000000000000000000 > 0) r = r * 0x80002c5c8dade4d71776c0f4dbea67d6 >> 127;
if (x & 0x80000000000000000000000000 > 0) r = r * 0x8000162e44eaf636526be456600bdbe4 >> 127;
if (x & 0x40000000000000000000000000 > 0) r = r * 0x80000b1721fa7c188307016c1cd4e8b6 >> 127;
if (x & 0x20000000000000000000000000 > 0) r = r * 0x8000058b90de7e4cecfc487503488bb1 >> 127;
if (x & 0x10000000000000000000000000 > 0) r = r * 0x800002c5c8678f36cbfce50a6de60b14 >> 127;
if (x & 0x8000000000000000000000000 > 0) r = r * 0x80000162e431db9f80b2347b5d62e516 >> 127;
if (x & 0x4000000000000000000000000 > 0) r = r * 0x800000b1721872d0c7b08cf1e0114152 >> 127;
if (x & 0x2000000000000000000000000 > 0) r = r * 0x80000058b90c1aa8a5c3736cb77e8dff >> 127;
if (x & 0x1000000000000000000000000 > 0) r = r * 0x8000002c5c8605a4635f2efc2362d978 >> 127;
if (x & 0x800000000000000000000000 > 0) r = r * 0x800000162e4300e635cf4a109e3939bd >> 127;
if (x & 0x400000000000000000000000 > 0) r = r * 0x8000000b17217ff81bef9c551590cf83 >> 127;
if (x & 0x200000000000000000000000 > 0) r = r * 0x800000058b90bfdd4e39cd52c0cfa27c >> 127;
if (x & 0x100000000000000000000000 > 0) r = r * 0x80000002c5c85fe6f72d669e0e76e411 >> 127;
if (x & 0x80000000000000000000000 > 0) r = r * 0x8000000162e42ff18f9ad35186d0df28 >> 127;
if (x & 0x40000000000000000000000 > 0) r = r * 0x80000000b17217f84cce71aa0dcfffe7 >> 127;
if (x & 0x20000000000000000000000 > 0) r = r * 0x8000000058b90bfc07a77ad56ed22aaa >> 127;
if (x & 0x10000000000000000000000 > 0) r = r * 0x800000002c5c85fdfc23cdead40da8d6 >> 127;
if (x & 0x8000000000000000000000 > 0) r = r * 0x80000000162e42fefc25eb1571853a66 >> 127;
if (x & 0x4000000000000000000000 > 0) r = r * 0x800000000b17217f7d97f692baacded5 >> 127;
if (x & 0x2000000000000000000000 > 0) r = r * 0x80000000058b90bfbead3b8b5dd254d7 >> 127;
if (x & 0x1000000000000000000000 > 0) r = r * 0x8000000002c5c85fdf4eedd62f084e67 >> 127;
if (x & 0x800000000000000000000 > 0) r = r * 0x800000000162e42fefa58aef378bf586 >> 127;
if (x & 0x400000000000000000000 > 0) r = r * 0x8000000000b17217f7d24a78a3c7ef02 >> 127;
if (x & 0x200000000000000000000 > 0) r = r * 0x800000000058b90bfbe9067c93e474a6 >> 127;
if (x & 0x100000000000000000000 > 0) r = r * 0x80000000002c5c85fdf47b8e5a72599f >> 127;
if (x & 0x80000000000000000000 > 0) r = r * 0x8000000000162e42fefa3bdb315934a2 >> 127;
if (x & 0x40000000000000000000 > 0) r = r * 0x80000000000b17217f7d1d7299b49c46 >> 127;
if (x & 0x20000000000000000000 > 0) r = r * 0x8000000000058b90bfbe8e9a8d1c4ea0 >> 127;
if (x & 0x10000000000000000000 > 0) r = r * 0x800000000002c5c85fdf4745969ea76f >> 127;
if (x & 0x8000000000000000000 > 0) r = r * 0x80000000000162e42fefa3a0df5373bf >> 127;
if (x & 0x4000000000000000000 > 0) r = r * 0x800000000000b17217f7d1cff4aac1e1 >> 127;
if (x & 0x2000000000000000000 > 0) r = r * 0x80000000000058b90bfbe8e7db95a2f1 >> 127;
if (x & 0x1000000000000000000 > 0) r = r * 0x8000000000002c5c85fdf473e61ae1f8 >> 127;
if (x & 0x800000000000000000 > 0) r = r * 0x800000000000162e42fefa39f121751c >> 127;
if (x & 0x400000000000000000 > 0) r = r * 0x8000000000000b17217f7d1cf815bb96 >> 127;
if (x & 0x200000000000000000 > 0) r = r * 0x800000000000058b90bfbe8e7bec1e0d >> 127;
if (x & 0x100000000000000000 > 0) r = r * 0x80000000000002c5c85fdf473dee5f17 >> 127;
if (x & 0x80000000000000000 > 0) r = r * 0x8000000000000162e42fefa39ef5438f >> 127;
if (x & 0x40000000000000000 > 0) r = r * 0x80000000000000b17217f7d1cf7a26c8 >> 127;
if (x & 0x20000000000000000 > 0) r = r * 0x8000000000000058b90bfbe8e7bcf4a4 >> 127;
if (x & 0x10000000000000000 > 0) r = r * 0x800000000000002c5c85fdf473de72a2 >> 127;
if (x & 0x8000000000000000 > 0) r = r * 0x80000000000000162e42fefa39ef3765 >> 127;
if (x & 0x4000000000000000 > 0) r = r * 0x800000000000000b17217f7d1cf79b37 >> 127;
if (x & 0x2000000000000000 > 0) r = r * 0x80000000000000058b90bfbe8e7bcd7d >> 127;
if (x & 0x1000000000000000 > 0) r = r * 0x8000000000000002c5c85fdf473de6b6 >> 127;
if (x & 0x800000000000000 > 0) r = r * 0x800000000000000162e42fefa39ef359 >> 127;
if (x & 0x400000000000000 > 0) r = r * 0x8000000000000000b17217f7d1cf79ac >> 127;
if (x & 0x200000000000000 > 0) r = r * 0x800000000000000058b90bfbe8e7bcd6 >> 127;
if (x & 0x100000000000000 > 0) r = r * 0x80000000000000002c5c85fdf473de6a >> 127;
if (x & 0x80000000000000 > 0) r = r * 0x8000000000000000162e42fefa39ef35 >> 127;
if (x & 0x40000000000000 > 0) r = r * 0x80000000000000000b17217f7d1cf79a >> 127;
if (x & 0x20000000000000 > 0) r = r * 0x8000000000000000058b90bfbe8e7bcd >> 127;
if (x & 0x10000000000000 > 0) r = r * 0x800000000000000002c5c85fdf473de6 >> 127;
if (x & 0x8000000000000 > 0) r = r * 0x80000000000000000162e42fefa39ef3 >> 127;
if (x & 0x4000000000000 > 0) r = r * 0x800000000000000000b17217f7d1cf79 >> 127;
if (x & 0x2000000000000 > 0) r = r * 0x80000000000000000058b90bfbe8e7bc >> 127;
if (x & 0x1000000000000 > 0) r = r * 0x8000000000000000002c5c85fdf473de >> 127;
if (x & 0x800000000000 > 0) r = r * 0x800000000000000000162e42fefa39ef >> 127;
if (x & 0x400000000000 > 0) r = r * 0x8000000000000000000b17217f7d1cf7 >> 127;
if (x & 0x200000000000 > 0) r = r * 0x800000000000000000058b90bfbe8e7b >> 127;
if (x & 0x100000000000 > 0) r = r * 0x80000000000000000002c5c85fdf473d >> 127;
if (x & 0x80000000000 > 0) r = r * 0x8000000000000000000162e42fefa39e >> 127;
if (x & 0x40000000000 > 0) r = r * 0x80000000000000000000b17217f7d1cf >> 127;
if (x & 0x20000000000 > 0) r = r * 0x8000000000000000000058b90bfbe8e7 >> 127;
if (x & 0x10000000000 > 0) r = r * 0x800000000000000000002c5c85fdf473 >> 127;
if (x & 0x8000000000 > 0) r = r * 0x80000000000000000000162e42fefa39 >> 127;
if (x & 0x4000000000 > 0) r = r * 0x800000000000000000000b17217f7d1c >> 127;
if (x & 0x2000000000 > 0) r = r * 0x80000000000000000000058b90bfbe8e >> 127;
if (x & 0x1000000000 > 0) r = r * 0x8000000000000000000002c5c85fdf47 >> 127;
if (x & 0x800000000 > 0) r = r * 0x800000000000000000000162e42fefa3 >> 127;
if (x & 0x400000000 > 0) r = r * 0x8000000000000000000000b17217f7d1 >> 127;
if (x & 0x200000000 > 0) r = r * 0x800000000000000000000058b90bfbe8 >> 127;
if (x & 0x100000000 > 0) r = r * 0x80000000000000000000002c5c85fdf4 >> 127;
if (x & 0x80000000 > 0) r = r * 0x8000000000000000000000162e42fefa >> 127;
if (x & 0x40000000 > 0) r = r * 0x80000000000000000000000b17217f7d >> 127;
if (x & 0x20000000 > 0) r = r * 0x8000000000000000000000058b90bfbe >> 127;
if (x & 0x10000000 > 0) r = r * 0x800000000000000000000002c5c85fdf >> 127;
if (x & 0x8000000 > 0) r = r * 0x80000000000000000000000162e42fef >> 127;
if (x & 0x4000000 > 0) r = r * 0x800000000000000000000000b17217f7 >> 127;
if (x & 0x2000000 > 0) r = r * 0x80000000000000000000000058b90bfb >> 127;
if (x & 0x1000000 > 0) r = r * 0x8000000000000000000000002c5c85fd >> 127;
if (x & 0x800000 > 0) r = r * 0x800000000000000000000000162e42fe >> 127;
if (x & 0x400000 > 0) r = r * 0x8000000000000000000000000b17217f >> 127;
if (x & 0x200000 > 0) r = r * 0x800000000000000000000000058b90bf >> 127;
if (x & 0x100000 > 0) r = r * 0x80000000000000000000000002c5c85f >> 127;
if (x & 0x80000 > 0) r = r * 0x8000000000000000000000000162e42f >> 127;
if (x & 0x40000 > 0) r = r * 0x80000000000000000000000000b17217 >> 127;
if (x & 0x20000 > 0) r = r * 0x8000000000000000000000000058b90b >> 127;
if (x & 0x10000 > 0) r = r * 0x800000000000000000000000002c5c85 >> 127;
if (x & 0x8000 > 0) r = r * 0x80000000000000000000000000162e42 >> 127;
if (x & 0x4000 > 0) r = r * 0x800000000000000000000000000b1721 >> 127;
if (x & 0x2000 > 0) r = r * 0x80000000000000000000000000058b90 >> 127;
if (x & 0x1000 > 0) r = r * 0x8000000000000000000000000002c5c8 >> 127;
if (x & 0x800 > 0) r = r * 0x800000000000000000000000000162e4 >> 127;
if (x & 0x400 > 0) r = r * 0x8000000000000000000000000000b172 >> 127;
if (x & 0x200 > 0) r = r * 0x800000000000000000000000000058b9 >> 127;
if (x & 0x100 > 0) r = r * 0x80000000000000000000000000002c5c >> 127;
if (x & 0x80 > 0) r = r * 0x8000000000000000000000000000162e >> 127;
if (x & 0x40 > 0) r = r * 0x80000000000000000000000000000b17 >> 127;
if (x & 0x20 > 0) r = r * 0x8000000000000000000000000000058b >> 127;
if (x & 0x10 > 0) r = r * 0x800000000000000000000000000002c5 >> 127;
if (x & 0x8 > 0) r = r * 0x80000000000000000000000000000162 >> 127;
if (x & 0x4 > 0) r = r * 0x800000000000000000000000000000b1 >> 127;
if (x & 0x2 > 0) r = r * 0x80000000000000000000000000000058 >> 127;
if (x & 0x1 > 0) r = r * 0x8000000000000000000000000000002c >> 127;

r >>= 127 - (x >> 121);

z = uint128(r);
}
}
}