-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #66 from morpho-org/fix/bound-constructor
fix(constructor): add parameter bounds & extract constants / wExp
- Loading branch information
Showing
7 changed files
with
191 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
/// @title ConstantsLib | ||
/// @author Morpho Labs | ||
/// @custom:contact security@morpho.org | ||
library ConstantsLib { | ||
/// @notice Maximum rate at target per second (scaled by WAD) (1B% APR). | ||
int256 internal constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days; | ||
|
||
/// @notice Mininimum rate at target per second (scaled by WAD) (0.1% APR). | ||
int256 internal constant MIN_RATE_AT_TARGET = int256(0.001 ether) / 365 days; | ||
|
||
/// @notice Maximum curve steepness allowed (scaled by WAD). | ||
int256 internal constant MAX_CURVE_STEEPNESS = 100 ether; | ||
|
||
/// @notice Maximum adjustment speed allowed (scaled by WAD). | ||
int256 internal constant MAX_ADJUSTMENT_SPEED = int256(1_000 ether) / 365 days; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {WAD_INT} from "../MathLib.sol"; | ||
|
||
/// @title ExpLib | ||
/// @author Morpho Labs | ||
/// @custom:contact security@morpho.org | ||
/// @notice Library to approximate the exponential function. | ||
library ExpLib { | ||
/// @dev ln(2). | ||
int256 internal constant LN_2_INT = 0.693147180559945309 ether; | ||
|
||
/// @dev ln(1e-18). | ||
int256 internal constant LN_WEI_INT = -41.446531673892822312 ether; | ||
|
||
/// @dev Above this bound, `wExp` is clipped to avoid overflowing when multiplied with 1 ether. | ||
/// @dev This upper bound corresponds to: ln(type(int256).max / 1e36) (scaled by WAD, floored). | ||
int256 internal constant WEXP_UPPER_BOUND = 93.859467695000404319 ether; | ||
|
||
/// @dev The value of wExp(`WEXP_UPPER_BOUND`). | ||
int256 internal constant WEXP_UPPER_VALUE = 57716089161558943949701069502944508345128.422502756744429568 ether; | ||
|
||
/// @dev Returns an approximation of exp. | ||
function wExp(int256 x) internal pure returns (int256) { | ||
unchecked { | ||
// If x < ln(1e-18) then exp(x) < 1e-18 so it is rounded to zero. | ||
if (x < LN_WEI_INT) return 0; | ||
// `wExp` is clipped to avoid overflowing when multiplied with 1 ether. | ||
if (x >= WEXP_UPPER_BOUND) return WEXP_UPPER_VALUE; | ||
|
||
// 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) ? -(LN_2_INT / 2) : (LN_2_INT / 2); | ||
// Safe unchecked because x is bounded. | ||
int256 q = (x + roundingAdjustment) / LN_2_INT; | ||
// Safe unchecked because |q * ln(2) - x| <= ln(2)/2. | ||
int256 r = x - q * LN_2_INT; | ||
|
||
// Compute e^r with a 2nd-order Taylor polynomial. | ||
// Safe unchecked because |r| < 1e18. | ||
int256 expR = WAD_INT + r + (r * r) / WAD_INT / 2; | ||
|
||
// Return e^x = 2^q * e^r. | ||
if (q >= 0) return expR << uint256(q); | ||
else return expR >> uint256(-q); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {MathLib, WAD_INT} from "../src/libraries/MathLib.sol"; | ||
import {ConstantsLib} from "../src/libraries/adaptive-curve/ConstantsLib.sol"; | ||
import {ExpLib} from "../src/libraries/adaptive-curve/ExpLib.sol"; | ||
import {wadExp} from "../lib/solmate/src/utils/SignedWadMath.sol"; | ||
import {MathLib as MorphoMathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; | ||
|
||
import "../lib/forge-std/src/Test.sol"; | ||
|
||
contract ExpLibTest is Test { | ||
using MathLib for int256; | ||
using MorphoMathLib for uint256; | ||
|
||
/// @dev ln(1e-9) truncated at 2 decimal places. | ||
int256 internal constant LN_GWEI_INT = -20.72 ether; | ||
|
||
function testWExp(int256 x) public { | ||
// Bounded to have sub-1% relative error. | ||
x = bound(x, LN_GWEI_INT, ExpLib.WEXP_UPPER_BOUND); | ||
|
||
assertApproxEqRel(ExpLib.wExp(x), wadExp(x), 0.01 ether); | ||
} | ||
|
||
function testWExpSmall(int256 x) public { | ||
x = bound(x, ExpLib.LN_WEI_INT, LN_GWEI_INT); | ||
|
||
assertApproxEqAbs(ExpLib.wExp(x), 0, 1e10); | ||
} | ||
|
||
function testWExpTooSmall(int256 x) public { | ||
x = bound(x, type(int256).min, ExpLib.LN_WEI_INT); | ||
|
||
assertEq(ExpLib.wExp(x), 0); | ||
} | ||
|
||
function testWExpTooLarge(int256 x) public { | ||
x = bound(x, ExpLib.WEXP_UPPER_BOUND, type(int256).max); | ||
|
||
assertEq(ExpLib.wExp(x), ExpLib.WEXP_UPPER_VALUE); | ||
} | ||
|
||
function testWExpDoesNotLeadToOverflow() public { | ||
assertGt(ExpLib.WEXP_UPPER_VALUE * 1e18, 0); | ||
} | ||
|
||
function testWExpContinuousUpperBound() public { | ||
assertApproxEqRel(ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND - 1), ExpLib.WEXP_UPPER_VALUE, 1e-10 ether); | ||
assertEq(_wExpUnbounded(ExpLib.WEXP_UPPER_BOUND), ExpLib.WEXP_UPPER_VALUE); | ||
} | ||
|
||
function testWExpPositive(int256 x) public { | ||
x = bound(x, 0, type(int256).max); | ||
|
||
assertGe(ExpLib.wExp(x), 1e18); | ||
} | ||
|
||
function testWExpNegative(int256 x) public { | ||
x = bound(x, type(int256).min, 0); | ||
|
||
assertLe(ExpLib.wExp(x), 1e18); | ||
} | ||
|
||
function testWExpWMulDownMaxRate() public pure { | ||
ExpLib.wExp(ExpLib.WEXP_UPPER_BOUND).wMulDown(ConstantsLib.MAX_RATE_AT_TARGET); | ||
} | ||
|
||
function _wExpUnbounded(int256 x) internal pure returns (int256) { | ||
unchecked { | ||
// 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) ? -(ExpLib.LN_2_INT / 2) : (ExpLib.LN_2_INT / 2); | ||
// Safe unchecked because x is bounded. | ||
int256 q = (x + roundingAdjustment) / ExpLib.LN_2_INT; | ||
// Safe unchecked because |q * ln(2) - x| <= ln(2)/2. | ||
int256 r = x - q * ExpLib.LN_2_INT; | ||
|
||
// Compute e^r with a 2nd-order Taylor polynomial. | ||
// Safe unchecked because |r| < 1e18. | ||
int256 expR = WAD_INT + r + (r * r) / WAD_INT / 2; | ||
|
||
// Return e^x = 2^q * e^r. | ||
if (q >= 0) return expR << uint256(q); | ||
else return expR >> uint256(-q); | ||
} | ||
} | ||
} |
Oops, something went wrong.