Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/LeverageManager.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;
// Dependency imports
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {ReentrancyGuardTransientUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
// Internal imports
import {IRebalanceAdapterBase} from "src/interfaces/IRebalanceAdapterBase.sol";
import {IBeaconProxyFactory} from "src/interfaces/IBeaconProxyFactory.sol";
import {ILendingAdapter} from "src/interfaces/ILendingAdapter.sol";
import {ILeverageManager} from "src/interfaces/ILeverageManager.sol";
import {ILeverageToken} from "src/interfaces/ILeverageToken.sol";
import {FeeManager} from "src/FeeManager.sol";
import {LeverageTokenState} from "src/types/DataTypes.sol";
import {LeverageToken} from "src/LeverageToken.sol";
import {
ActionData,
ActionType,
ExternalAction,
LeverageTokenConfig,
BaseLeverageTokenConfig,
RebalanceAction
} from "src/types/DataTypes.sol";
/**
* @dev The LeverageManager contract is an upgradeable core contract that is responsible for managing the creation of LeverageTokens.
* It also acts as an entry point for users to mint and redeem LeverageTokens (shares), and for
* rebalancers to rebalance LeverageTokens.
*
* LeverageTokens are ERC20 tokens that are akin to shares in an ERC-4626 vault - they represent a claim on the equity held by
* the LeverageToken. They can be created on this contract by calling `createNewLeverageToken`, and their configuration on the
* LeverageManager is immutable.
* Note: Although the LeverageToken configuration saved on the LeverageManager is immutable, the configured LendingAdapter and
* RebalanceAdapter for the LeverageToken may be upgradeable contracts.
*
* The LeverageManager also inherits the `FeeManager` contract, which is used to manage LeverageToken fees (which accrue to
* the share value of the LeverageToken) and the treasury fees.
*
* For mints of LeverageTokens (shares), the collateral and debt required is calculated by using the LeverageToken's
* current collateral ratio. As such, the collateral ratio after a mint must be equal to the collateral ratio before a
* mint, within some rounding error.
*
* [CAUTION]
* ====
* - LeverageTokens are susceptible to inflation attacks like ERC-4626 vaults:
* "In empty (or nearly empty) ERC-4626 vaults, mints are at high risk of being stolen through frontrunning
* with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
* attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
* mint of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Redeems may
* similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
* verifying the amount received is as expected, using a wrapper that performs these checks such as
* https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]."
*
* As such it is highly recommended that LeverageToken creators make an initial mint of a non-trivial amount of equity.
* It is also recommended to use a router that performs slippage checks when minting and redeeming.
*
* - LeverageToken creation is permissionless and can be configured with arbitrary lending adapters, rebalance adapters, and
* underlying collateral and debt assets. As such, the adapters and tokens used by a LeverageToken are part of the risk
* profile of the LeverageToken, and should be carefully considered by users before using a LeverageToken.
*
* - LeverageTokens can be configured with arbitrary lending adapters, thus LeverageTokens are directly affected by the
* specific mechanisms of the underlying lending market that their lending adapter integrates with. As mentioned above,
* it is highly recommended that users research and understand the lending adapter used by the LeverageToken they are
* considering using. Some examples:
* - Morpho: Users should be aware that Morpho market creation is permissionless, and that the price oracle used by
* by the market may be manipulatable.
* - Aave v3: Allows rehypothecation of collateral, which may lead to reverts when trying to remove collateral from the
* market during redeems and rebalances.
*
* @custom:contact security@seamlessprotocol.com
*/
contract LeverageManager is
ILeverageManager,
AccessControlUpgradeable,
ReentrancyGuardTransientUpgradeable,
FeeManager,
UUPSUpgradeable
{
// Base collateral ratio constant, 1e18 means that collateral / debt ratio is 1:1
uint256 public constant BASE_RATIO = 1e18;
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
/// @dev Struct containing all state for the LeverageManager contract
/// @custom:storage-location erc7201:seamless.contracts.storage.LeverageManager
struct LeverageManagerStorage {
/// @dev Factory for deploying new LeverageTokens
IBeaconProxyFactory tokenFactory;
/// @dev LeverageToken address => Base config for LeverageToken
mapping(ILeverageToken token => BaseLeverageTokenConfig) config;
}
function _getLeverageManagerStorage() internal pure returns (LeverageManagerStorage storage $) {
// slither-disable-next-line assembly
assembly {
// keccak256(abi.encode(uint256(keccak256("seamless.contracts.storage.LeverageManager")) - 1)) & ~bytes32(uint256(0xff));
$.slot := 0x326e20d598a681eb69bc11b5176604d340fccf9864170f09484f3c317edf3600
}
}
function initialize(address initialAdmin, address treasury, IBeaconProxyFactory leverageTokenFactory)
external
initializer
{
__FeeManager_init(initialAdmin, treasury);
__ReentrancyGuardTransient_init();
_grantRole(DEFAULT_ADMIN_ROLE, initialAdmin);
_getLeverageManagerStorage().tokenFactory = leverageTokenFactory;
emit LeverageManagerInitialized(leverageTokenFactory);
}
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
/// @inheritdoc ILeverageManager
function convertCollateralToDebt(ILeverageToken token, uint256 collateral, Math.Rounding rounding)
external
view
returns (uint256 debt)
{
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 totalCollateral = lendingAdapter.getCollateral();
uint256 totalDebt = lendingAdapter.getDebt();
return _convertCollateralToDebt(token, lendingAdapter, collateral, totalCollateral, totalDebt, rounding);
}
/// @inheritdoc ILeverageManager
function convertCollateralToShares(ILeverageToken token, uint256 collateral, Math.Rounding rounding)
external
view
returns (uint256 shares)
{
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 totalSupply = getFeeAdjustedTotalSupply(token);
return _convertCollateralToShares(token, lendingAdapter, collateral, totalSupply, rounding);
}
/// @inheritdoc ILeverageManager
function convertDebtToCollateral(ILeverageToken token, uint256 debt, Math.Rounding rounding)
external
view
returns (uint256 collateral)
{
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 totalCollateral = lendingAdapter.getCollateral();
uint256 totalDebt = lendingAdapter.getDebt();
if (totalDebt == 0) {
if (totalCollateral == 0) {
// Initial state: no collateral or debt, use initial collateral ratio
uint256 initialCollateralRatio = getLeverageTokenInitialCollateralRatio(token);
return lendingAdapter.convertDebtToCollateralAsset(
Math.mulDiv(debt, initialCollateralRatio, BASE_RATIO, rounding)
);
}
// Liquidated state: no debt but collateral exists, cannot convert
return 0;
}
return Math.mulDiv(debt, totalCollateral, totalDebt, rounding);
}
/// @inheritdoc ILeverageManager
function convertSharesToCollateral(ILeverageToken token, uint256 shares, Math.Rounding rounding)
external
view
returns (uint256 collateral)
{
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 totalCollateral = lendingAdapter.getCollateral();
uint256 totalSupply = getFeeAdjustedTotalSupply(token);
return _convertSharesToCollateral(token, lendingAdapter, shares, totalCollateral, totalSupply, rounding);
}
/// @inheritdoc ILeverageManager
function convertSharesToDebt(ILeverageToken token, uint256 shares, Math.Rounding rounding)
external
view
returns (uint256 debt)
{
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 totalDebt = lendingAdapter.getDebt();
uint256 totalSupply = getFeeAdjustedTotalSupply(token);
return _convertSharesToDebt(token, lendingAdapter, shares, totalDebt, totalSupply, rounding);
}
/// @inheritdoc ILeverageManager
function convertToAssets(ILeverageToken token, uint256 shares)
external
view
returns (uint256 equityInCollateralAsset)
{
uint256 totalEquityInCollateralAsset = getLeverageTokenLendingAdapter(token).getEquityInCollateralAsset();
uint256 totalSupply = getFeeAdjustedTotalSupply(token);
// slither-disable-next-line incorrect-equality,timestamp
if (totalSupply == 0) {
return 0;
}
return Math.mulDiv(shares, totalEquityInCollateralAsset, totalSupply, Math.Rounding.Floor);
}
/// @inheritdoc ILeverageManager
function convertToShares(ILeverageToken token, uint256 equityInCollateralAsset)
external
view
returns (uint256 shares)
{
uint256 totalEquityInCollateralAsset = getLeverageTokenLendingAdapter(token).getEquityInCollateralAsset();
uint256 totalSupply = getFeeAdjustedTotalSupply(token);
if (totalEquityInCollateralAsset == 0) {
return 0;
}
return Math.mulDiv(equityInCollateralAsset, totalSupply, totalEquityInCollateralAsset, Math.Rounding.Floor);
}
/// @inheritdoc ILeverageManager
function getLeverageTokenFactory() public view returns (IBeaconProxyFactory factory) {
return _getLeverageManagerStorage().tokenFactory;
}
/// @inheritdoc ILeverageManager
function getLeverageTokenCollateralAsset(ILeverageToken token) public view returns (IERC20 collateralAsset) {
return getLeverageTokenLendingAdapter(token).getCollateralAsset();
}
/// @inheritdoc ILeverageManager
function getLeverageTokenDebtAsset(ILeverageToken token) public view returns (IERC20 debtAsset) {
return getLeverageTokenLendingAdapter(token).getDebtAsset();
}
/// @inheritdoc ILeverageManager
function getLeverageTokenRebalanceAdapter(ILeverageToken token)
public
view
returns (IRebalanceAdapterBase module)
{
return _getLeverageManagerStorage().config[token].rebalanceAdapter;
}
/// @inheritdoc ILeverageManager
function getLeverageTokenConfig(ILeverageToken token) external view returns (LeverageTokenConfig memory config) {
BaseLeverageTokenConfig memory baseConfig = _getLeverageManagerStorage().config[token];
uint256 mintTokenFee = getLeverageTokenActionFee(token, ExternalAction.Mint);
uint256 redeemTokenFee = getLeverageTokenActionFee(token, ExternalAction.Redeem);
return LeverageTokenConfig({
lendingAdapter: baseConfig.lendingAdapter,
rebalanceAdapter: baseConfig.rebalanceAdapter,
mintTokenFee: mintTokenFee,
redeemTokenFee: redeemTokenFee
});
}
/// @inheritdoc ILeverageManager
function getLeverageTokenLendingAdapter(ILeverageToken token) public view returns (ILendingAdapter adapter) {
return _getLeverageManagerStorage().config[token].lendingAdapter;
}
/// @inheritdoc ILeverageManager
function getLeverageTokenInitialCollateralRatio(ILeverageToken token) public view returns (uint256) {
uint256 initialCollateralRatio =
getLeverageTokenRebalanceAdapter(token).getLeverageTokenInitialCollateralRatio(token);
if (initialCollateralRatio <= BASE_RATIO) {
revert InvalidLeverageTokenInitialCollateralRatio(initialCollateralRatio);
}
return initialCollateralRatio;
}
/// @inheritdoc ILeverageManager
function getLeverageTokenState(ILeverageToken token) external view returns (LeverageTokenState memory state) {
return _getLeverageTokenState(getLeverageTokenLendingAdapter(token));
}
/// @inheritdoc ILeverageManager
function createNewLeverageToken(LeverageTokenConfig calldata tokenConfig, string memory name, string memory symbol)
external
nonReentrant
returns (ILeverageToken token)
{
IBeaconProxyFactory tokenFactory = getLeverageTokenFactory();
// slither-disable-next-line reentrancy-events
token = ILeverageToken(
tokenFactory.createProxy(
abi.encodeWithSelector(LeverageToken.initialize.selector, address(this), name, symbol),
bytes32(tokenFactory.numProxies())
)
);
_getLeverageManagerStorage().config[token] = BaseLeverageTokenConfig({
lendingAdapter: tokenConfig.lendingAdapter,
rebalanceAdapter: tokenConfig.rebalanceAdapter
});
_setLeverageTokenActionFee(token, ExternalAction.Mint, tokenConfig.mintTokenFee);
_setLeverageTokenActionFee(token, ExternalAction.Redeem, tokenConfig.redeemTokenFee);
_setNewLeverageTokenManagementFee(token);
tokenConfig.lendingAdapter.postLeverageTokenCreation(msg.sender, address(token));
tokenConfig.rebalanceAdapter.postLeverageTokenCreation(msg.sender, address(token));
emit LeverageTokenCreated(
token,
tokenConfig.lendingAdapter.getCollateralAsset(),
tokenConfig.lendingAdapter.getDebtAsset(),
tokenConfig
);
return token;
}
/// @inheritdoc ILeverageManager
function previewDeposit(ILeverageToken token, uint256 collateral) public view returns (ActionData memory) {
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 feeAdjustedTotalSupply = getFeeAdjustedTotalSupply(token);
uint256 debt = _convertCollateralToDebt(
token,
lendingAdapter,
collateral,
lendingAdapter.getCollateral(),
lendingAdapter.getDebt(),
Math.Rounding.Floor
);
uint256 shares =
_convertCollateralToShares(token, lendingAdapter, collateral, feeAdjustedTotalSupply, Math.Rounding.Floor);
(uint256 sharesAfterFee, uint256 sharesFee, uint256 treasuryFee) =
_computeFeesForGrossShares(token, shares, ExternalAction.Mint);
return ActionData({
collateral: collateral,
debt: debt,
shares: sharesAfterFee,
tokenFee: sharesFee,
treasuryFee: treasuryFee
});
}
/// @inheritdoc ILeverageManager
function previewMint(ILeverageToken token, uint256 shares) public view returns (ActionData memory) {
(uint256 grossShares, uint256 sharesFee, uint256 treasuryFee) =
_computeFeesForNetShares(token, shares, ExternalAction.Mint);
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 feeAdjustedTotalSupply = getFeeAdjustedTotalSupply(token);
uint256 collateral = _convertSharesToCollateral(
token,
lendingAdapter,
grossShares,
lendingAdapter.getCollateral(),
feeAdjustedTotalSupply,
Math.Rounding.Ceil
);
uint256 debt = _convertCollateralToDebt(
token,
lendingAdapter,
collateral,
lendingAdapter.getCollateral(),
lendingAdapter.getDebt(),
Math.Rounding.Floor
);
return ActionData({
collateral: collateral,
debt: debt,
shares: shares,
tokenFee: sharesFee,
treasuryFee: treasuryFee
});
}
/// @inheritdoc ILeverageManager
function previewRedeem(ILeverageToken token, uint256 shares) public view returns (ActionData memory) {
(uint256 sharesAfterFees, uint256 sharesFee, uint256 treasuryFee) =
_computeFeesForGrossShares(token, shares, ExternalAction.Redeem);
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 feeAdjustedTotalSupply = getFeeAdjustedTotalSupply(token);
// The redeemer receives collateral and repays debt for the net shares after fees are subtracted. The amount of
// shares their balance is decreased by is that net share amount (which is burned) plus the fees.
// - the treasury fee shares are given to the treasury
// - the token fee shares are burned to increase share value
uint256 collateral = _convertSharesToCollateral(
token,
lendingAdapter,
sharesAfterFees,
lendingAdapter.getCollateral(),
feeAdjustedTotalSupply,
Math.Rounding.Floor
);
uint256 debt = _convertSharesToDebt(
token, lendingAdapter, sharesAfterFees, lendingAdapter.getDebt(), feeAdjustedTotalSupply, Math.Rounding.Ceil
);
return ActionData({
collateral: collateral,
debt: debt,
shares: shares,
tokenFee: sharesFee,
treasuryFee: treasuryFee
});
}
/// @inheritdoc ILeverageManager
function previewWithdraw(ILeverageToken token, uint256 collateral) public view returns (ActionData memory) {
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
uint256 feeAdjustedTotalSupply = getFeeAdjustedTotalSupply(token);
// The withdrawer receives their specified collateral amount and pays debt for the shares that can be exchanged
// for the collateral amount. The amount of shares their balance is decreased by is that share amount (which is
// burned) plus the fees.
// - the treasury fee shares are given to the treasury
// - the token fee shares are burned to increase share value
uint256 shares =
_convertCollateralToShares(token, lendingAdapter, collateral, feeAdjustedTotalSupply, Math.Rounding.Ceil);
uint256 debt = _convertCollateralToDebt(
token,
lendingAdapter,
collateral,
lendingAdapter.getCollateral(),
lendingAdapter.getDebt(),
Math.Rounding.Ceil
);
(uint256 sharesAfterFees, uint256 sharesFee, uint256 treasuryFee) =
_computeFeesForNetShares(token, shares, ExternalAction.Redeem);
return ActionData({
collateral: collateral,
debt: debt,
shares: sharesAfterFees,
tokenFee: sharesFee,
treasuryFee: treasuryFee
});
}
/// @inheritdoc ILeverageManager
function deposit(ILeverageToken token, uint256 collateral, uint256 minShares)
external
nonReentrant
returns (ActionData memory actionData)
{
// Management fee is calculated from the total supply of the LeverageToken, so we need to charge it first
// before total supply is updated due to the mint
chargeManagementFee(token);
ActionData memory depositData = previewDeposit(token, collateral);
// slither-disable-next-line timestamp
if (depositData.shares < minShares) {
revert SlippageTooHigh(depositData.shares, minShares);
}
_mint(token, depositData);
return depositData;
}
/// @inheritdoc ILeverageManager
function mint(ILeverageToken token, uint256 shares, uint256 maxCollateral)
external
nonReentrant
returns (ActionData memory actionData)
{
// Management fee is calculated from the total supply of the LeverageToken, so we need to charge it first
// before total supply is updated due to the mint
chargeManagementFee(token);
ActionData memory mintData = previewMint(token, shares);
// slither-disable-next-line timestamp
if (mintData.collateral > maxCollateral) {
revert SlippageTooHigh(mintData.collateral, maxCollateral);
}
_mint(token, mintData);
return mintData;
}
/// @inheritdoc ILeverageManager
function rebalance(
ILeverageToken leverageToken,
RebalanceAction[] calldata actions,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 amountIn,
uint256 amountOut
) external nonReentrant {
_transferTokens(tokenIn, msg.sender, address(this), amountIn);
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(leverageToken);
// Check if the LeverageToken is eligible for rebalance
LeverageTokenState memory stateBefore = _getLeverageTokenState(lendingAdapter);
IRebalanceAdapterBase rebalanceAdapter = getLeverageTokenRebalanceAdapter(leverageToken);
if (!rebalanceAdapter.isEligibleForRebalance(leverageToken, stateBefore, msg.sender)) {
revert LeverageTokenNotEligibleForRebalance();
}
for (uint256 i = 0; i < actions.length; i++) {
_executeLendingAdapterAction(leverageToken, actions[i].actionType, actions[i].amount);
}
// Validate the LeverageToken state after rebalancing
if (!rebalanceAdapter.isStateAfterRebalanceValid(leverageToken, stateBefore)) {
revert InvalidLeverageTokenStateAfterRebalance(leverageToken);
}
_transferTokens(tokenOut, address(this), msg.sender, amountOut);
LeverageTokenState memory stateAfter = _getLeverageTokenState(lendingAdapter);
emit Rebalance(leverageToken, msg.sender, stateBefore, stateAfter, actions);
}
/// @inheritdoc ILeverageManager
function redeem(ILeverageToken token, uint256 shares, uint256 minCollateral)
external
nonReentrant
returns (ActionData memory actionData)
{
// Management fee is calculated from the total supply of the LeverageToken, so we need to claim it first
// before total supply is updated due to the redeem
chargeManagementFee(token);
ActionData memory redeemData = previewRedeem(token, shares);
// slither-disable-next-line timestamp
if (redeemData.collateral < minCollateral) {
revert SlippageTooHigh(redeemData.collateral, minCollateral);
}
_redeem(token, redeemData);
return redeemData;
}
/// @inheritdoc ILeverageManager
function withdraw(ILeverageToken token, uint256 collateral, uint256 maxShares)
external
nonReentrant
returns (ActionData memory actionData)
{
// Management fee is calculated from the total supply of the LeverageToken, so we need to claim it first
// before total supply is updated due to the redeem
chargeManagementFee(token);
ActionData memory withdrawData = previewWithdraw(token, collateral);
// slither-disable-next-line timestamp
if (withdrawData.shares > maxShares) {
revert SlippageTooHigh(withdrawData.shares, maxShares);
}
_redeem(token, withdrawData);
return withdrawData;
}
/// @notice Converts collateral to debt given the state of the LeverageToken
/// @param token LeverageToken to convert collateral for
/// @param lendingAdapter Lending adapter of the LeverageToken
/// @param collateral Collateral to convert to debt
/// @param totalCollateral Total collateral of the LeverageToken
/// @param totalDebt Total debt of the LeverageToken
/// @param rounding Rounding mode
/// @return debt Debt
function _convertCollateralToDebt(
ILeverageToken token,
ILendingAdapter lendingAdapter,
uint256 collateral,
uint256 totalCollateral,
uint256 totalDebt,
Math.Rounding rounding
) internal view returns (uint256 debt) {
if (totalCollateral == 0) {
if (totalDebt == 0) {
// Initial state: no collateral or debt, use initial collateral ratio
uint256 initialCollateralRatio = getLeverageTokenInitialCollateralRatio(token);
return lendingAdapter.convertCollateralToDebtAsset(
Math.mulDiv(collateral, BASE_RATIO, initialCollateralRatio, rounding)
);
}
// Liquidated state: no collateral but debt exists, cannot convert
return 0;
}
return Math.mulDiv(collateral, totalDebt, totalCollateral, rounding);
}
/// @notice Converts collateral to shares given the state of the LeverageToken
/// @param token LeverageToken to convert collateral for
/// @param lendingAdapter Lending adapter of the LeverageToken
/// @param collateral Collateral to convert to shares
/// @param totalSupply Total supply of shares of the LeverageToken
/// @param rounding Rounding mode
/// @return shares Shares
function _convertCollateralToShares(
ILeverageToken token,
ILendingAdapter lendingAdapter,
uint256 collateral,
uint256 totalSupply,
Math.Rounding rounding
) internal view returns (uint256 shares) {
uint256 totalCollateral = lendingAdapter.getCollateral();
// slither-disable-next-line incorrect-equality,timestamp
if (totalSupply == 0) {
uint256 initialCollateralRatio = getLeverageTokenInitialCollateralRatio(token);
uint256 equityInCollateralAsset =
Math.mulDiv(collateral, initialCollateralRatio - BASE_RATIO, initialCollateralRatio, rounding);
uint256 leverageTokenDecimals = IERC20Metadata(address(token)).decimals();
uint256 collateralDecimals = IERC20Metadata(address(lendingAdapter.getCollateralAsset())).decimals();
// If collateral asset has more decimals than leverage token, we scale down the equity in collateral asset
// Otherwise we scale up the equity in collateral asset
if (collateralDecimals > leverageTokenDecimals) {
uint256 scalingFactor = 10 ** (collateralDecimals - leverageTokenDecimals);
return equityInCollateralAsset / scalingFactor;
} else {
uint256 scalingFactor = 10 ** (leverageTokenDecimals - collateralDecimals);
return equityInCollateralAsset * scalingFactor;
}
}
// If total supply != 0 and total collateral is zero, the LeverageToken was fully liquidated. In this case,
// no amount of collateral can be converted to shares. An implication of this is that new mints of shares
// will not be possible for the LeverageToken, unless a deposit of collateral occurs directly on the
// lending adapter of the LeverageToken resulting in totalCollateral no longer being zero.
if (totalCollateral == 0) {
return 0;
}
return Math.mulDiv(collateral, totalSupply, totalCollateral, rounding);
}
/// @notice Converts shares to collateral given the state of the LeverageToken
/// @param token LeverageToken to convert shares for
/// @param lendingAdapter Lending adapter of the LeverageToken
/// @param shares Shares to convert to collateral
/// @param totalCollateral Total collateral of the LeverageToken
/// @param totalSupply Total supply of shares of the LeverageToken
/// @param rounding Rounding mode
function _convertSharesToCollateral(
ILeverageToken token,
ILendingAdapter lendingAdapter,
uint256 shares,
uint256 totalCollateral,
uint256 totalSupply,
Math.Rounding rounding
) internal view returns (uint256 collateral) {
// slither-disable-next-line incorrect-equality,timestamp
if (totalSupply == 0) {
uint256 leverageTokenDecimals = IERC20Metadata(address(token)).decimals();
uint256 collateralDecimals = IERC20Metadata(address(lendingAdapter.getCollateralAsset())).decimals();
uint256 initialCollateralRatio = getLeverageTokenInitialCollateralRatio(token);
// If collateral asset has more decimals than leverage token, we scale down the equity in collateral asset
// Otherwise we scale up the equity in collateral asset
if (collateralDecimals > leverageTokenDecimals) {
uint256 scalingFactor = 10 ** (collateralDecimals - leverageTokenDecimals);
return Math.mulDiv(
shares * scalingFactor, initialCollateralRatio, initialCollateralRatio - BASE_RATIO, rounding
);
} else {
uint256 scalingFactor = 10 ** (leverageTokenDecimals - collateralDecimals);
return Math.mulDiv(
shares, initialCollateralRatio, (initialCollateralRatio - BASE_RATIO) * scalingFactor, rounding
);
}
}
return Math.mulDiv(shares, totalCollateral, totalSupply, rounding);
}
/// @notice Converts shares to debt given the state of the LeverageToken
/// @param token LeverageToken to convert shares for
/// @param lendingAdapter Lending adapter of the LeverageToken
/// @param shares Shares to convert to debt
/// @param totalDebt Total debt of the LeverageToken
/// @param totalSupply Total supply of shares of the LeverageToken
/// @param rounding Rounding mode
function _convertSharesToDebt(
ILeverageToken token,
ILendingAdapter lendingAdapter,
uint256 shares,
uint256 totalDebt,
uint256 totalSupply,
Math.Rounding rounding
) internal view returns (uint256 debt) {
// slither-disable-next-line incorrect-equality,timestamp
if (totalSupply == 0) {
uint256 leverageTokenDecimals = IERC20Metadata(address(token)).decimals();
uint256 collateralDecimals = IERC20Metadata(address(lendingAdapter.getCollateralAsset())).decimals();
uint256 initialCollateralRatio = getLeverageTokenInitialCollateralRatio(token);
// If collateral asset has more decimals than leverage token, we scale down the equity in collateral asset
// Otherwise we scale up the equity in collateral asset
if (collateralDecimals > leverageTokenDecimals) {
uint256 scalingFactor = 10 ** (collateralDecimals - leverageTokenDecimals);
return lendingAdapter.convertCollateralToDebtAsset(
Math.mulDiv(shares * scalingFactor, BASE_RATIO, initialCollateralRatio - BASE_RATIO, rounding)
);
} else {
uint256 scalingFactor = 10 ** (leverageTokenDecimals - collateralDecimals);
return lendingAdapter.convertCollateralToDebtAsset(
Math.mulDiv(shares, BASE_RATIO, (initialCollateralRatio - BASE_RATIO) * scalingFactor, rounding)
);
}
}
return Math.mulDiv(shares, totalDebt, totalSupply, rounding);
}
/// @notice Executes actions on the LendingAdapter for a specific LeverageToken
/// @param token LeverageToken to execute action for
/// @param actionType Type of the action to execute
/// @param amount Amount to execute action with
function _executeLendingAdapterAction(ILeverageToken token, ActionType actionType, uint256 amount) internal {
ILendingAdapter lendingAdapter = getLeverageTokenLendingAdapter(token);
if (actionType == ActionType.AddCollateral) {
IERC20 collateralAsset = lendingAdapter.getCollateralAsset();
// slither-disable-next-line reentrancy-events
SafeERC20.forceApprove(collateralAsset, address(lendingAdapter), amount);
// slither-disable-next-line reentrancy-events
lendingAdapter.addCollateral(amount);
} else if (actionType == ActionType.RemoveCollateral) {
// slither-disable-next-line reentrancy-events
lendingAdapter.removeCollateral(amount);
} else if (actionType == ActionType.Borrow) {
// slither-disable-next-line reentrancy-events
lendingAdapter.borrow(amount);
} else if (actionType == ActionType.Repay) {
IERC20 debtAsset = lendingAdapter.getDebtAsset();
// slither-disable-next-line reentrancy-events
SafeERC20.forceApprove(debtAsset, address(lendingAdapter), amount);
// slither-disable-next-line reentrancy-events
lendingAdapter.repay(amount);
}
}
//// @notice Returns all data required to describe current LeverageToken state - collateral, debt, equity and collateral ratio
/// @param lendingAdapter LendingAdapter of the LeverageToken
/// @return state LeverageToken state
function _getLeverageTokenState(ILendingAdapter lendingAdapter)
internal
view
returns (LeverageTokenState memory state)
{
uint256 collateral = lendingAdapter.getCollateralInDebtAsset();
uint256 debt = lendingAdapter.getDebt();
uint256 equity = lendingAdapter.getEquityInDebtAsset();
uint256 collateralRatio =
debt > 0 ? Math.mulDiv(collateral, BASE_RATIO, debt, Math.Rounding.Floor) : type(uint256).max;
return LeverageTokenState({
collateralInDebtAsset: collateral,
debt: debt,
equity: equity,
collateralRatio: collateralRatio
});
}
/// @notice Helper function for executing a mint action on a LeverageToken
/// @param token LeverageToken to mint shares for
/// @param mintData Action data for the mint
function _mint(ILeverageToken token, ActionData memory mintData) internal {
// Take collateral asset from sender
IERC20 collateralAsset = getLeverageTokenCollateralAsset(token);
SafeERC20.safeTransferFrom(collateralAsset, msg.sender, address(this), mintData.collateral);
// Add collateral to LeverageToken
_executeLendingAdapterAction(token, ActionType.AddCollateral, mintData.collateral);
// Borrow and send debt assets to caller
_executeLendingAdapterAction(token, ActionType.Borrow, mintData.debt);
SafeERC20.safeTransfer(getLeverageTokenDebtAsset(token), msg.sender, mintData.debt);
// Charge treasury fee
_chargeTreasuryFee(token, mintData.treasuryFee);
// Mint shares to user
// slither-disable-next-line reentrancy-events
token.mint(msg.sender, mintData.shares);
// Emit event and explicit return statement
emit Mint(token, msg.sender, mintData);
}
/// @notice Helper function for executing a redeem action on a LeverageToken
/// @param token LeverageToken to redeem shares for
/// @param redeemData Action data for the redeem
function _redeem(ILeverageToken token, ActionData memory redeemData) internal {
// Burn shares from user and total supply
token.burn(msg.sender, redeemData.shares);
// Mint shares to treasury for the treasury action fee
_chargeTreasuryFee(token, redeemData.treasuryFee);
// Take assets from sender and repay the debt
SafeERC20.safeTransferFrom(getLeverageTokenDebtAsset(token), msg.sender, address(this), redeemData.debt);
_executeLendingAdapterAction(token, ActionType.Repay, redeemData.debt);
// Remove collateral from lending pool
_executeLendingAdapterAction(token, ActionType.RemoveCollateral, redeemData.collateral);
// Send collateral assets to sender
SafeERC20.safeTransfer(getLeverageTokenCollateralAsset(token), msg.sender, redeemData.collateral);
// Emit event and explicit return statement
emit Redeem(token, msg.sender, redeemData);
}
/// @notice Helper function for transferring tokens, or no-op if token is 0 address
/// @param token Token to transfer
/// @param from Address to transfer tokens from
/// @param to Address to transfer tokens to
/// @dev If from address is this smart contract it will use the regular transfer function otherwise it will use transferFrom
function _transferTokens(IERC20 token, address from, address to, uint256 amount) internal {
if (address(token) == address(0)) {
return;
}
if (from == address(this)) {
SafeERC20.safeTransfer(token, to, amount);
} else {
SafeERC20.safeTransferFrom(token, from, to, amount);
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.20;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
* See {_onlyProxy}.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2²⁵⁶ + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= prod1) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
for (uint256 i = 0; i < byteArray.length; ++i) {
if (byteArray[i] != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We
Submitted on: 2025-09-30 10:12:55
Comments
Log in to comment.
No comments yet.