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: compound interest #222

Merged
merged 10 commits into from
Aug 9, 2023
2 changes: 1 addition & 1 deletion src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
21 changes: 21 additions & 0 deletions src/libraries/FixedPointMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ 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.
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
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;
pakim249CAL marked this conversation as resolved.
Show resolved Hide resolved

return firstTerm + secondTerm + thirdTerm;
}

/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
Expand All @@ -55,4 +65,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))
}
}
}
31 changes: 31 additions & 0 deletions test/forge/Math.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +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);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}