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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/morpho-blue"]
path = lib/morpho-blue
url = https://github.com/morpho-labs/morpho-blue
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
1 change: 1 addition & 0 deletions lib/solmate
Submodule solmate added at fadb2e
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ forge-std/=lib/forge-std/src/
morpho-blue/=lib/morpho-blue/src/
openzeppelin-contracts/=lib/morpho-blue/lib/openzeppelin-contracts/
openzeppelin/=lib/morpho-blue/lib/openzeppelin-contracts/contracts/
solmate/=lib/solmate/src/
1 change: 1 addition & 0 deletions src/irm/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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_UNDERFLOW = "wExp underflow";
string internal constant WEXP_OVERFLOW = "wExp overflow";
string internal constant NOT_MORPHO = "not Morpho";
}
25 changes: 11 additions & 14 deletions src/irm/libraries/MathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,20 @@ library MathLib {
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;
// Revert if x < min + (ln(2)/2).
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
require(x >= type(int256).min + LN2_INT / 2, ErrorsLib.WEXP_UNDERFLOW);

// Decompose x as x = q * ln(2) + r with q an integer and -ln(2)/2 < r <= ln(2)/2.
// q = x / ln(2) rounded half toward zero.
int256 roundingAdjustment = (x < 0) ? -(LN2_INT / 2) : (LN2_INT / 2);
// Safe unchecked because x is bounded.
int256 q = (x + roundingAdjustment) / LN2_INT;
// 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.
int256 thirdTerm = r.wMulDown(r) / 2;
// Safe unchecked because |r| < 1.
int256 fourthTerm = thirdTerm.wMulDown(r) / 3;
// 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);
// Compute e^r with a 2nd-order Taylor polynomial.
// Safe unchecked because |r| < 1, expR < 2 and the sum is positive.
uint256 expR = uint256(WAD_INT + r + r.wMulDown(r) / 2);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

// Return e^x = 2^q * e^r.
if (q >= 0) return expR << uint256(q);
Expand Down
70 changes: 13 additions & 57 deletions test/irm/MathLibTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,35 @@
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "solmate/utils/SignedWadMath.sol";
import "../../src/irm/libraries/MathLib.sol";
import "../../src/irm/libraries/ErrorsLib.sol";

contract MathLibTest is Test {
using MathLib for uint128;
using MathLib for uint256;

function testWExp() public {
assertApproxEqRel(MathLib.wExp(-14 ether), 0.00000083131 ether, 0.01 ether);
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);
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);
}
int256 private constant LN2_INT = 0.693147180559945309 ether;

function testWExp(int256 x) public {
// Assume x < 256 * -ln(2) ~ -177.
vm.assume(x > -176 ether);
// Assume x < ln(2**256) ~ 177.
vm.assume(x < 176 ether);
if (x >= 0) assertGe(MathLib.wExp(x), WAD + uint256(x));
if (x < 0) assertLe(MathLib.wExp(x), WAD);
x = bound(x, -27 ether, 94 ether);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
assertApproxEqRel(MathLib.wExp(x), uint256(wadExp(x)), 0.01 ether);
}

function testWExpRef(int256 x) public {
x = bound(x, -3 ether, 3 ether);
assertApproxEqRel(MathLib.wExp(x), wExpRef(x), 0.03 ether);
function testWExpSmall(int256 x) public {
x = bound(x, type(int256).min + LN2_INT / 2, -178 ether);
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
assertEq(MathLib.wExp(x), 0);
}

function testWExpRevertTooSmall(int256 x) public {
vm.assume(x <= -178 ether);
function testWExpTooSmall(int256 x) public {
x = bound(x, type(int256).min, type(int256).min + LN2_INT / 2 - 1);
vm.expectRevert(bytes(ErrorsLib.WEXP_UNDERFLOW));
assertEq(MathLib.wExp(x), 0);
}

function testWExpRevertTooBig(int256 x) public {
function testWExpTooLarge(int256 x) public {
vm.assume(x >= 178 ether);
vm.expectRevert();
vm.expectRevert(bytes(ErrorsLib.WEXP_OVERFLOW));
MathLib.wExp(x);
}
}

function wExpRef(int256 x) pure returns (uint256) {
// `N` should be even otherwise the result can be negative.
int256 N = 64;
int256 res = WAD_INT;
int256 monomial = WAD_INT;
for (int256 k = 1; k <= N; k++) {
monomial = monomial * x / WAD_INT / k;
res += monomial;
}
// Safe "unchecked" cast because `N` is even.
return uint256(res);
}