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

perf wExp #12

Merged
merged 16 commits into from
Sep 4, 2023
1 change: 1 addition & 0 deletions src/irm/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pragma solidity ^0.8.0;
library ErrorsLib {
string internal constant MAX_INT128_EXCEEDED = "max int128 exceeded";
string internal constant INPUT_TOO_LARGE = "input too large";
string internal constant WEXP_OVERFLOW = "wExp overflow";
string internal constant NOT_MORPHO = "not Morpho";
}
21 changes: 12 additions & 9 deletions src/irm/libraries/MathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import {WAD, MathLib as MorphoMathLib} from "morpho-blue/libraries/MathLib.sol";
import {ErrorsLib} from "./ErrorsLib.sol";

int256 constant WAD_INT = int256(WAD);

Expand All @@ -15,29 +16,31 @@ library MathLib {
int256 private constant LN2_INT = 0.693147180559945309 ether;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

/// @dev Returns an approximation of exp.
/// @dev Expects input between -ln(2**256) and ln(2**256).
function wExp(int256 x) internal pure returns (uint256) {
unchecked {
// Revert if x > ln(2^256-1) ~ 177.
require(x <= 177.44567822334599921 ether, ErrorsLib.WEXP_OVERFLOW);

// Decompose x as x = q * ln(2) + r with q an integer and -ln(2) < r < ln(2).
int256 q = x / LN2_INT;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
// Safe unchecked * because |q * LN2_INT| <= x.
// Safe unchecked because |q * LN2_INT| <= x.
int256 r = x - q * LN2_INT;

// Compute e^r.
int256 firstTerm = WAD_INT;
int256 secondTerm = r;
// Safe unchecked * because |r| < 1.
// Safe unchecked because |r| < 1.
int256 thirdTerm = r.wMulDown(r) / 2;
// Safe unchecked * because |r| < 1.
// Safe unchecked because |r| < 1.
int256 fourthTerm = thirdTerm.wMulDown(r) / 3;
// Safe unchecked * because |r| < 1.
int256 fifthTerm = fourthTerm.wMulDown(r) / 12;
// Safe unchecked + because expR < 2.
// Safe unchecked because |r| < 1.
int256 fifthTerm = fourthTerm.wMulDown(r) / 4;
// Safe unchecked because expR < 2 and the sum is positive.
uint256 expR = uint256(firstTerm + secondTerm + thirdTerm + fourthTerm + fifthTerm);

// Return e^x = 2^q * e^r.
if (q >= 0) return (1 << uint256(q)) * expR;
else return expR / (1 << uint256(-q));
if (q >= 0) return expR << uint256(q);
else return expR >> uint256(-q);
}
}

Expand Down
33 changes: 33 additions & 0 deletions test/irm/MathLibTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,35 @@ contract MathLibTest is Test {
using MathLib for uint256;

function testWExp() public {
assertApproxEqRel(MathLib.wExp(-14 ether), 0.00000083131 ether, 0.01 ether);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
assertApproxEqRel(MathLib.wExp(-13 ether), 0.00000226033 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-12 ether), 0.00000614421 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-11 ether), 0.00001670164 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-10 ether), 0.00004539992 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-9 ether), 0.0001234098 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-8 ether), 0.00033546262 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-7 ether), 0.00091188196 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-6 ether), 0.00247875217 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-5 ether), 0.00673794699 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-4 ether), 0.01831563888 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-3 ether), 0.04978706836 ether, 0.01 ether);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
assertApproxEqRel(MathLib.wExp(-2 ether), 0.13533528323 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(-1 ether), 0.36787944117 ether, 0.01 ether);
assertEq(MathLib.wExp(0 ether), 1.0 ether);
assertApproxEqRel(MathLib.wExp(1 ether), 2.71828182846 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(2 ether), 7.38905609893 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(3 ether), 20.0855369232 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(4 ether), 54.5981500331 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(5 ether), 148.413159103 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(6 ether), 403.428793493 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(7 ether), 1096.63315843 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(8 ether), 2980.95798704 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(9 ether), 8103.08392758 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(10 ether), 22026.4657948 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(11 ether), 59874.1417152 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(12 ether), 162754.791419 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(13 ether), 442413.392009 ether, 0.01 ether);
assertApproxEqRel(MathLib.wExp(14 ether), 1202604.28416 ether, 0.01 ether);
}

function testWExp(int256 x) public {
Expand All @@ -31,6 +53,17 @@ contract MathLibTest is Test {
x = bound(x, -3 ether, 3 ether);
assertApproxEqRel(MathLib.wExp(x), wExpRef(x), 0.03 ether);
}

function testWExpRevertTooSmall(int256 x) public {
vm.assume(x <= -178 ether);
assertEq(MathLib.wExp(x), 0);
}

function testWExpRevertTooBig(int256 x) public {
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
vm.assume(x >= 178 ether);
vm.expectRevert();
MathLib.wExp(x);
}
}

function wExpRef(int256 x) pure returns (uint256) {
Expand Down