Skip to content

Commit

Permalink
feat: add ContinuousIndexingMath and lib argument naming (#39)
Browse files Browse the repository at this point in the history
Co-authored-by: Pierrick Turelier <[email protected]>
  • Loading branch information
deluca-mike and PierrickGT authored Oct 30, 2024
1 parent c995528 commit 3692db1
Show file tree
Hide file tree
Showing 20 changed files with 781 additions and 186 deletions.
2 changes: 1 addition & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{
"files": "*.sol",
"options": {
"compiler": "0.8.26",
"compiler": ">=0.8.20 <0.9.0",
"parser": "solidity-parse",
"tabWidth": 4,
"singleQuote": false,
Expand Down
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"visibility-modifier-order": "error",
"compiler-version": [
"warn",
"0.8.26"
">=0.8.20 <0.9.0"
],
"func-visibility": [
"warn",
Expand Down
20 changes: 10 additions & 10 deletions src/libs/Bytes32String.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ pragma solidity >=0.8.20 <0.9.0;
* @author M^0 Labs
*/
library Bytes32String {
function toBytes32(string memory input_) internal pure returns (bytes32) {
return bytes32(abi.encodePacked(input_));
function toBytes32(string memory input) internal pure returns (bytes32) {
return bytes32(abi.encodePacked(input));
}

function toString(bytes32 input_) internal pure returns (string memory) {
uint256 length_;
function toString(bytes32 input) internal pure returns (string memory) {
uint256 length;

while (length_ < 32 && uint8(input_[length_]) != 0) {
++length_;
while (length < 32 && uint8(input[length]) != 0) {
++length;
}

bytes memory name_ = new bytes(length_);
bytes memory name = new bytes(length);

for (uint256 index_; index_ < length_; ++index_) {
name_[index_] = input_[index_];
for (uint256 index; index < length; ++index) {
name[index] = input[index];
}

return string(name_);
return string(name);
}
}
110 changes: 110 additions & 0 deletions src/libs/ContinuousIndexingMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.20 <0.9.0;

import { IndexingMath } from "./IndexingMath.sol";

/**
* @title Arithmetic library with operations for calculating continuous indexing.
* @author M^0 Labs
*/
library ContinuousIndexingMath {
/* ============ Variables ============ */

/// @notice The scaling of indexes for exponent math.
uint56 internal constant EXP_SCALED_ONE = IndexingMath.EXP_SCALED_ONE;

/// @notice The number of seconds in a year.
uint32 internal constant SECONDS_PER_YEAR = 31_536_000;

/// @notice 100% in basis points.
uint16 internal constant BPS_SCALED_ONE = 1e4;

/* ============ Internal View/Pure Functions ============ */

/**
* @notice Helper function to calculate `(index * deltaIndex) / EXP_SCALED_ONE`, rounded down.
* @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
*/
function multiplyIndicesDown(uint128 index, uint48 deltaIndex) internal pure returns (uint144) {
unchecked {
return uint144((uint256(index) * deltaIndex) / EXP_SCALED_ONE);
}
}

/**
* @notice Helper function to calculate `(index * deltaIndex) / EXP_SCALED_ONE`, rounded up.
* @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
*/
function multiplyIndicesUp(uint128 index, uint48 deltaIndex) internal pure returns (uint144) {
unchecked {
return uint144((uint256(index) * deltaIndex + (EXP_SCALED_ONE - 1)) / EXP_SCALED_ONE);
}
}

/**
* @notice Helper function to calculate e^rt (continuous compounding formula).
* @dev `uint64 yearlyRate` can accommodate 1000% interest per year.
* @dev `uint32 time` can accommodate 100 years.
* @dev `type(uint64).max * type(uint32).max / SECONDS_PER_YEAR` fits in a `uint72`.
*/
function getContinuousIndex(uint64 yearlyRate, uint32 time) internal pure returns (uint48) {
unchecked {
// NOTE: Casting `uint256(yearlyRate) * time` to a `uint72` is safe because the largest value is
// `type(uint64).max * type(uint32).max / SECONDS_PER_YEAR`, which is less than `type(uint72).max`.
return exponent(uint72((uint256(yearlyRate) * time) / SECONDS_PER_YEAR));
}
}

/**
* @notice Helper function to calculate y = e^x using R(4,4) Padé approximation:
* e(x) = (1 + x/2 + 3(x^2)/28 + x^3/84 + x^4/1680) / (1 - x/2 + 3(x^2)/28 - x^3/84 + x^4/1680)
* See: https://en.wikipedia.org/wiki/Pad%C3%A9_table
* See: https://www.wolframalpha.com/input?i=PadeApproximant%5Bexp%5Bx%5D%2C%7Bx%2C0%2C%7B4%2C+4%7D%7D%5D
* Despite itself being a whole number, `x` represents a real number scaled by `EXP_SCALED_ONE`, thus
* allowing for y = e^x where x is a real number.
* @dev Output for a `uint72` input `x` will fit in `uint48`
*/
function exponent(uint72 x) internal pure returns (uint48) {
// NOTE: This can be done unchecked even for `x = type(uint72).max`.
// Verify by removing `unchecked` and running `test_exponent()`.
unchecked {
uint256 x2 = uint256(x) * x;

// `additiveTerms` is `(1 + 3(x^2)/28 + x^4/1680)`, and scaled by `84e27`.
// NOTE: `84e27` the cleanest and largest scalar, given the various intermediate overflow possibilities.
// NOTE: The resulting `(x2 * x2) / 20e21` term has been split to avoid overflow of `x2 * x2`.
uint256 additiveTerms = 84e27 + (9e3 * x2) + ((x2 / 2e11) * (x2 / 1e11));

// `differentTerms` is `(- x/2 - x^3/84)`, but positive (will be subtracted later), and scaled by `84e27`.
uint256 differentTerms = uint256(x) * (42e15 + (x2 / 1e9));

// Result needs to be scaled by `1e12`.
// NOTE: Can cast to `uint48` because contents can never be larger than `type(uint48).max` for any `x`.
// Max `y` is ~200e12, before falling off. See links above for reference.
return uint48(((additiveTerms + differentTerms) * 1e12) / (additiveTerms - differentTerms));
}
}

/**
* @notice Helper function to convert a scaled 12-decimal percentage to basis points.
* @param input A percentage represented as a scaled 12-decimal number.
* @return The percentage represented as basis points.
*/
function convertToBasisPoints(uint64 input) internal pure returns (uint40) {
unchecked {
return uint40((uint256(input) * BPS_SCALED_ONE) / EXP_SCALED_ONE);
}
}

/**
* @notice Helper function to convert basis points to a scaled 12-decimal percentage.
* @param input A percentage represented as basis points.
* @return The percentage represented as a scaled 12-decimal number.
*/
function convertFromBasisPoints(uint32 input) internal pure returns (uint64) {
unchecked {
return uint64((uint256(input) * EXP_SCALED_ONE) / BPS_SCALED_ONE);
}
}
}
40 changes: 20 additions & 20 deletions src/libs/ContractHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,51 +21,51 @@ pragma solidity >=0.8.20 <0.9.0;
*/
library ContractHelper {
/**
* @notice Returns the expected address of a contract deployed by `account_` with transaction count `nonce_`.
* @param account_ The address of the account deploying a contract.
* @param nonce_ The nonce used in the deployment transaction.
* @return contract_ The expected address of the deployed contract.
* @notice Returns the expected address of a contract deployed by `account` with transaction count `nonce`.
* @param account The address of the account deploying a contract.
* @param nonce The nonce used in the deployment transaction.
* @return The expected address of the deployed contract.
*/
function getContractFrom(address account_, uint256 nonce_) internal pure returns (address contract_) {
function getContractFrom(address account, uint256 nonce) internal pure returns (address) {
return
address(
uint160(
uint256(
keccak256(
nonce_ == 0x00
? abi.encodePacked(bytes1(0xd6), bytes1(0x94), account_, bytes1(0x80))
: nonce_ <= 0x7f
? abi.encodePacked(bytes1(0xd6), bytes1(0x94), account_, uint8(nonce_))
: nonce_ <= 0xff
nonce == 0x00
? abi.encodePacked(bytes1(0xd6), bytes1(0x94), account, bytes1(0x80))
: nonce <= 0x7f
? abi.encodePacked(bytes1(0xd6), bytes1(0x94), account, uint8(nonce))
: nonce <= 0xff
? abi.encodePacked(
bytes1(0xd7),
bytes1(0x94),
account_,
account,
bytes1(0x81),
uint8(nonce_)
uint8(nonce)
)
: nonce_ <= 0xffff
: nonce <= 0xffff
? abi.encodePacked(
bytes1(0xd8),
bytes1(0x94),
account_,
account,
bytes1(0x82),
uint16(nonce_)
uint16(nonce)
)
: nonce_ <= 0xffffff
: nonce <= 0xffffff
? abi.encodePacked(
bytes1(0xd9),
bytes1(0x94),
account_,
account,
bytes1(0x83),
uint24(nonce_)
uint24(nonce)
)
: abi.encodePacked(
bytes1(0xda),
bytes1(0x94),
account_,
account,
bytes1(0x84),
uint32(nonce_)
uint32(nonce)
)
)
)
Expand Down
52 changes: 31 additions & 21 deletions src/libs/IndexingMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,79 +19,89 @@ library IndexingMath {
/// @notice Emitted when a division by zero occurs.
error DivisionByZero();

/* ============ Internal View/Pure Functions ============ */
/* ============ Exposed Functions ============ */

/**
* @notice Helper function to calculate `(x * EXP_SCALED_ONE) / y`, rounded down.
* @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
*/
function divide240By128Down(uint240 x_, uint128 y_) internal pure returns (uint112) {
if (y_ == 0) revert DivisionByZero();
function divide240By128Down(uint240 x, uint128 y) internal pure returns (uint112) {
if (y == 0) revert DivisionByZero();

unchecked {
// NOTE: While `uint256(x) * EXP_SCALED_ONE` can technically overflow, these divide/multiply functions are
// only used for the purpose of principal/present amount calculations for continuous indexing, and
// so for an `x` to be large enough to overflow this, it would have to be a possible result of
// `multiply112By128Down` or `multiply112By128Up`, which would already satisfy
// `uint256(x) * EXP_SCALED_ONE < type(uint240).max`.
return UIntMath.safe112((uint256(x_) * EXP_SCALED_ONE) / y_);
return UIntMath.safe112((uint256(x) * EXP_SCALED_ONE) / y);
}
}

/**
* @notice Helper function to calculate `(x * EXP_SCALED_ONE) / y`, rounded up.
* @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
*/
function divide240By128Up(uint240 x_, uint128 y_) internal pure returns (uint112) {
if (y_ == 0) revert DivisionByZero();
function divide240By128Up(uint240 x, uint128 y) internal pure returns (uint112) {
if (y == 0) revert DivisionByZero();

unchecked {
// NOTE: While `uint256(x) * EXP_SCALED_ONE` can technically overflow, these divide/multiply functions are
// only used for the purpose of principal/present amount calculations for continuous indexing, and
// so for an `x` to be large enough to overflow this, it would have to be a possible result of
// `multiply112By128Down` or `multiply112By128Up`, which would already satisfy
// `uint256(x) * EXP_SCALED_ONE < type(uint240).max`.
return UIntMath.safe112(((uint256(x_) * EXP_SCALED_ONE) + y_ - 1) / y_);
return UIntMath.safe112(((uint256(x) * EXP_SCALED_ONE) + y - 1) / y);
}
}

/**
* @notice Helper function to calculate `(x * y) / EXP_SCALED_ONE`, rounded down.
* @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
*/
function multiply112By128Down(uint112 x_, uint128 y_) internal pure returns (uint240) {
function multiply112By128Down(uint112 x, uint128 y) internal pure returns (uint240) {
unchecked {
return uint240((uint256(x_) * y_) / EXP_SCALED_ONE);
return uint240((uint256(x) * y) / EXP_SCALED_ONE);
}
}

/**
* @notice Helper function to calculate `(x * index) / EXP_SCALED_ONE`, rounded up.
* @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
*/
function multiply112By128Up(uint112 x, uint128 index) internal pure returns (uint240 z) {
unchecked {
return uint240(((uint256(x) * index) + (EXP_SCALED_ONE - 1)) / EXP_SCALED_ONE);
}
}

/**
* @dev Returns the present amount (rounded down) given the principal amount and an index.
* @param principalAmount_ The principal amount.
* @param index_ An index.
* @param principalAmount The principal amount.
* @param index An index.
* @return The present amount rounded down.
*/
function getPresentAmountRoundedDown(uint112 principalAmount_, uint128 index_) internal pure returns (uint240) {
return multiply112By128Down(principalAmount_, index_);
function getPresentAmountRoundedDown(uint112 principalAmount, uint128 index) internal pure returns (uint240) {
return multiply112By128Down(principalAmount, index);
}

/**
* @dev Returns the principal amount given the present amount, using the current index.
* @param presentAmount_ The present amount.
* @param index_ An index.
* @param presentAmount The present amount.
* @param index An index.
* @return The principal amount rounded down.
*/
function getPrincipalAmountRoundedDown(uint240 presentAmount_, uint128 index_) internal pure returns (uint112) {
return divide240By128Down(presentAmount_, index_);
function getPrincipalAmountRoundedDown(uint240 presentAmount, uint128 index) internal pure returns (uint112) {
return divide240By128Down(presentAmount, index);
}

/**
* @dev Returns the principal amount given the present amount, using the current index.
* @param presentAmount_ The present amount.
* @param index_ An index.
* @param presentAmount The present amount.
* @param index An index.
* @return The principal amount rounded up.
*/
function getPrincipalAmountRoundedUp(uint240 presentAmount_, uint128 index_) internal pure returns (uint112) {
return divide240By128Up(presentAmount_, index_);
function getPrincipalAmountRoundedUp(uint240 presentAmount, uint128 index) internal pure returns (uint112) {
return divide240By128Up(presentAmount, index);
}
}
18 changes: 9 additions & 9 deletions src/libs/SignatureChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ library SignatureChecker {
bytes32 digest,
bytes memory signature
) internal view returns (bool) {
(bool success, bytes memory result) = signer.staticcall(
(bool success_, bytes memory result_) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (digest, signature))
);

return
success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector);
success_ &&
result_.length >= 32 &&
abi.decode(result_, (bytes32)) == bytes32(IERC1271.isValidSignature.selector);
}

/**
Expand Down Expand Up @@ -122,11 +122,11 @@ library SignatureChecker {

/**
* @dev Returns whether an ECDSA/secp256k1 short signature is valid for a signer and digest.
* @param signer The address of the account purported to have signed.
* @param digest The hash of the data that was signed.
* @param r An ECDSA/secp256k1 signature parameter.
* @param vs An ECDSA/secp256k1 short signature parameter.
* @return Whether the signature is valid or not.
* @param signer The address of the account purported to have signed.
* @param digest The hash of the data that was signed.
* @param r An ECDSA/secp256k1 signature parameter.
* @param vs An ECDSA/secp256k1 short signature parameter.
* @return Whether the signature is valid or not.
*/
function isValidECDSASignature(address signer, bytes32 digest, bytes32 r, bytes32 vs) internal pure returns (bool) {
return validateECDSASignature(signer, digest, r, vs) == Error.NoError;
Expand Down
Loading

0 comments on commit 3692db1

Please sign in to comment.