// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ModuleBase } from "common/Lib.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { SafeCastLib } from "solady/utils/SafeCastLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import {
LiqRequest,
MultiDstMultiVaultStateReq,
MultiDstSingleVaultStateReq,
MultiVaultSFData,
MultiXChainMultiVaultWithdraw,
MultiXChainSingleVaultWithdraw,
SingleDirectMultiVaultStateReq,
SingleDirectSingleVaultStateReq,
SingleVaultSFData,
SingleXChainMultiVaultStateReq,
SingleXChainMultiVaultWithdraw,
SingleXChainSingleVaultStateReq,
SingleXChainSingleVaultWithdraw,
VaultConfig,
VaultData,
VaultLib
} from "types/Lib.sol";
/// @title AssetsManager
/// @notice Implementation of crosschain portfolio-management module
/// @dev Extends ModuleBase contract and implements portfolio management functionalities
contract AssetsManager is ModuleBase {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* LIBRARIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Library for vault-related operations
using VaultLib for VaultData;
/// @dev Safe casting operations for uint
using SafeCastLib for uint256;
/// @dev Safe transfer operations for ERC20 tokens
using SafeTransferLib for address;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Thrown when there are not enough assets to fulfill a request
error InsufficientAssets();
/// @notice Thrown when attempting to interact with a vault that is not listed in the portfolio
error VaultNotListed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when investing vault idle assets
event Invest(uint256 amount);
/// @dev Emitted when divesting vault idle assets
event Divest(uint256 amount);
/// @dev Emitted when cross-chain investment is settled
event SettleXChainInvest(uint256 indexed superformId, uint256 assets);
/// @dev Emitted when cross-chain investment is settled
event SettleXChainDivest(uint256 assets);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INVEST */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Invests assets from this vault into a single target vault within the same chain
/// @dev Only callable by addresses with the MANAGER_ROLE
/// @param vaultAddress The address of the target vault to invest in
/// @param assets The amount of assets to invest
/// @param minSharesOut The minimum amount of shares expected to receive from the investment
/// @return shares The number of shares received from the target vault
function investSingleDirectSingleVault(
address vaultAddress,
uint256 assets,
uint256 minSharesOut
)
public
onlyRoles(MANAGER_ROLE)
returns (uint256 shares)
{
// Ensure the target vault is in the approved list
if (!isVaultListed(vaultAddress)) revert VaultNotListed();
// Record the balance before deposit to calculate received shares
uint256 balanceBefore = vaultAddress.balanceOf(address(this));
// Deposit assets into the target vault
ERC4626(vaultAddress).deposit(assets, address(this));
// Calculate the number of shares received
shares = vaultAddress.balanceOf(address(this)) - balanceBefore;
// Ensure the received shares meet the minimum expected assets
if (shares < minSharesOut) {
revert InsufficientAssets();
}
// Update the vault's internal accounting
uint128 amountUint128 = assets.toUint128();
_totalIdle -= amountUint128;
_totalDebt += amountUint128;
vaults[_vaultToSuperformId[vaultAddress]].totalDebt += amountUint128;
emit Invest(assets);
return shares;
}
/// @notice Invests assets from this vault into multiple target vaults within the same chain
/// @dev Calls investSingleDirectSingleVault for each target vault
/// @param vaultAddresses An array of addresses of the target vaults to invest in
/// @param assets An array of amounts to invest in each corresponding vault
/// @param minSharesOuts An array of minimum amounts of shares expected from each investment
/// @return shares An array of the number of shares received from each target vault
function investSingleDirectMultiVault(
address[] calldata vaultAddresses,
uint256[] calldata assets,
uint256[] calldata minSharesOuts
)
external
returns (uint256[] memory shares)
{
shares = new uint256[](vaultAddresses.length);
for (uint256 i = 0; i < vaultAddresses.length; ++i) {
shares[i] = investSingleDirectSingleVault(vaultAddresses[i], assets[i], minSharesOuts[i]);
}
}
/// @notice Invests assets from this vault into a single target vault on a different chain
/// @dev Only callable by addresses with the MANAGER_ROLE
/// @param req Crosschain deposit request
function investSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 amount = gateway.investSingleXChainSingleVault{ value: msg.value }(req);
// Update the vault's internal accounting
uint128 amountUint128 = amount.toUint128();
_totalIdle -= amountUint128;
emit Invest(amount);
}
/// @notice Placeholder for investing in multiple vaults across chains
/// @param req Crosschain deposit request
function investSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 totalAmount = gateway.investSingleXChainMultiVault{ value: msg.value }(req);
_totalIdle -= totalAmount.toUint128();
emit Invest(totalAmount);
}
/// @notice Placeholder for investing multiple assets in a single vault across chains
/// @dev Not implemented yet
function investMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 totalAmount = gateway.investMultiXChainSingleVault{ value: msg.value }(req);
_totalIdle -= totalAmount.toUint128();
emit Invest(totalAmount);
}
/// @notice Placeholder for investing multiple assets in multiple vaults across chains
/// @dev Not implemented yet
function investMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 totalAmount = gateway.investMultiXChainMultiVault{ value: msg.value }(req);
_totalIdle -= totalAmount.toUint128();
emit Invest(totalAmount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DIVEST */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Withdraws assets from a single vault on the same chain
/// @dev This function redeems shares from an ERC4626 vault and updates internal accounting.
/// If all shares are withdrawn, it removes the total debt for that vault.
/// Only callable by addresses with MANAGER_ROLE.
/// @param vaultAddress The address of the vault to withdraw from
/// @param shares The amount of shares to redeem
/// @param minAssetsOut The minimum amount of assets expected to receive
/// @return assets The amount of assets actually withdrawn
function divestSingleDirectSingleVault(
address vaultAddress,
uint256 shares,
uint256 minAssetsOut
)
public
onlyRoles(MANAGER_ROLE)
returns (uint256 assets)
{
if (!isVaultListed(vaultAddress)) revert VaultNotListed();
uint256 sharesValue = ERC4626(vaultAddress).convertToAssets(shares).toUint128();
// Record the balance before deposit to calculate received assets
uint256 balanceBefore = asset().balanceOf(address(this));
// Deposit assets into the target vault
ERC4626(vaultAddress).redeem(shares, address(this), address(this));
// Calculate the number of assets received
assets = asset().balanceOf(address(this)) - balanceBefore;
// Ensure the received assets meet the minimum expected amount
if (assets < minAssetsOut) {
revert InsufficientAssets();
}
// Update the vault's internal accounting
_totalIdle += assets.toUint128();
_totalDebt = _sub0(_totalDebt, sharesValue).toUint128();
vaults[_vaultToSuperformId[vaultAddress]].totalDebt =
_sub0(vaults[_vaultToSuperformId[vaultAddress]].totalDebt, sharesValue).toUint128();
emit Divest(sharesValue);
return assets;
}
/// @notice Withdraws assets from multiple vaults on the same chain
/// @dev Iteratively calls divestSingleDirectSingleVault for each vault.
/// Only callable by addresses with MANAGER_ROLE.
/// @param vaultAddresses Array of vault addresses to withdraw from
/// @param shares Array of share amounts to withdraw from each vault
/// @param minAssetsOuts Array of minimum expected asset amounts for each withdrawal
/// @return assets Array of actual asset amounts withdrawn from each vault
function divestSingleDirectMultiVault(
address[] calldata vaultAddresses,
uint256[] calldata shares,
uint256[] calldata minAssetsOuts
)
external
returns (uint256[] memory assets)
{
assets = new uint256[](vaultAddresses.length);
for (uint256 i = 0; i < vaultAddresses.length; ++i) {
assets[i] = divestSingleDirectSingleVault(vaultAddresses[i], shares[i], minAssetsOuts[i]);
}
}
/// @notice Withdraws assets from a single vault on a different chain
/// @dev Initiates a cross-chain withdrawal through the gateway contract.
/// Updates debt tracking for the source vault.
/// Only callable by addresses with MANAGER_ROLE.
/// @param req The withdrawal request containing target chain, vault, and amount details
function divestSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 sharesValue = gateway.divestSingleXChainSingleVault{ value: msg.value }(req, true);
_totalDebt = _sub0(_totalDebt, sharesValue).toUint128();
vaults[req.superformData.superformId].totalDebt =
_sub0(vaults[req.superformData.superformId].totalDebt, sharesValue).toUint128();
emit Divest(sharesValue);
}
/// @notice Withdraws assets from multiple vaults on a single different chain
/// @dev Processes withdrawals from multiple vaults on the same target chain.
/// Updates debt tracking for all source vaults.
/// Only callable by addresses with MANAGER_ROLE.
/// @param req The withdrawal request containing target chain and multiple vault details
function divestSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 totalAmount = gateway.divestSingleXChainMultiVault{ value: msg.value }(req, true);
for (uint256 i = 0; i < req.superformsData.superformIds.length;) {
uint256 superformId = req.superformsData.superformIds[i];
VaultData memory vault = vaults[superformId];
uint256 divestAmount = vault.convertToAssets(req.superformsData.amounts[i], asset(), true);
vault.totalDebt = _sub0(vaults[superformId].totalDebt, divestAmount).toUint128();
vaults[superformId] = vault;
unchecked {
++i;
}
}
_totalDebt = _sub0(_totalDebt, totalAmount).toUint128();
emit Divest(totalAmount);
}
/// @notice Withdraws assets from a single vault across multiple chains
/// @dev Initiates withdrawals from the same vault type across different chains.
/// Updates debt tracking for all source vaults.
/// Only callable by addresses with MANAGER_ROLE.
/// @param req The withdrawal request containing multiple chain and single vault details
function divestMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 totalAmount = gateway.divestMultiXChainSingleVault{ value: msg.value }(req, true);
for (uint256 i = 0; i < req.superformsData.length;) {
uint256 superformId = req.superformsData[i].superformId;
VaultData memory vault = vaults[superformId];
uint256 divestAmount = vault.convertToAssets(req.superformsData[i].amount, asset(), true);
vault.totalDebt = _sub0(vaults[superformId].totalDebt, divestAmount).toUint128();
vaults[superformId] = vault;
unchecked {
++i;
}
}
_totalDebt = _sub0(_totalDebt, totalAmount).toUint128();
emit Divest(totalAmount);
}
/// @notice Withdraws assets from multiple vaults across multiple chains
/// @dev Processes withdrawals from different vaults across multiple chains.
/// Updates debt tracking for all source vaults.
/// Only callable by addresses with MANAGER_ROLE.
/// @param req The withdrawal request containing multiple chain and multiple vault details
function divestMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req)
external
payable
onlyRoles(MANAGER_ROLE)
{
uint256 totalAmount = gateway.divestMultiXChainMultiVault{ value: msg.value }(req, true);
for (uint256 i = 0; i < req.superformsData.length;) {
uint256[] memory superformIds = req.superformsData[i].superformIds;
for (uint256 j = 0; j < superformIds.length;) {
uint256 superformId = superformIds[j];
VaultData memory vault = vaults[superformId];
uint256 divestAmount = vault.convertToAssets(req.superformsData[i].amounts[j], asset(), true);
vault.totalDebt = _sub0(vaults[superformId].totalDebt, divestAmount).toUint128();
vaults[superformId] = vault;
unchecked {
++j;
}
}
unchecked {
++i;
}
}
_totalDebt = _sub0(_totalDebt, totalAmount).toUint128();
emit Divest(totalAmount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SETTLEMENT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Settles a cross-chain investment by updating vault accounting
/// @param superformId The ID of the superform being settled
/// @param bridgedAssets The amount of assets that were bridged
/// @dev Only callable by the gateway contract
function settleXChainInvest(uint256 superformId, uint256 bridgedAssets) public {
if (msg.sender != address(gateway)) revert Unauthorized();
_totalDebt += bridgedAssets.toUint128();
vaults[superformId].totalDebt += bridgedAssets.toUint128();
emit SettleXChainInvest(superformId, bridgedAssets);
}
/// @notice Settles a cross-chain divestment by updating vault accounting
/// @param withdrawnAssets The amount of assets that were withdrawn
/// @dev Only callable by the gateway contract
function settleXChainDivest(uint256 withdrawnAssets) public {
if (msg.sender != address(gateway)) revert Unauthorized();
_totalIdle += withdrawnAssets.toUint128();
emit SettleXChainDivest(withdrawnAssets);
}
/// @dev Helper function to fetch module function selectors
function selectors() public pure returns (bytes4[] memory) {
bytes4[] memory s = new bytes4[](14);
s[0] = this.investSingleDirectSingleVault.selector;
s[1] = this.investSingleDirectMultiVault.selector;
s[2] = this.investSingleXChainSingleVault.selector;
s[3] = this.investSingleXChainMultiVault.selector;
s[4] = this.investMultiXChainSingleVault.selector;
s[5] = this.investMultiXChainMultiVault.selector;
s[6] = this.divestSingleDirectSingleVault.selector;
s[7] = this.divestSingleDirectMultiVault.selector;
s[8] = this.divestSingleXChainSingleVault.selector;
s[9] = this.divestSingleXChainMultiVault.selector;
s[10] = this.divestMultiXChainSingleVault.selector;
s[11] = this.divestMultiXChainMultiVault.selector;
s[12] = this.settleXChainInvest.selector;
s[13] = this.settleXChainDivest.selector;
return s;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
struct PendingRoot {
/// @dev The submitted pending root.
bytes32 root;
/// @dev The optional ipfs hash containing metadata about the root (e.g. the merkle tree itself).
bytes32 ipfsHash;
/// @dev The timestamp at which the pending root can be accepted.
uint256 validAt;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
/// the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is invalid.
error InvalidPermit();
/// @dev The permit has expired.
error PermitExpired();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The storage slot for the total supply.
uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
/// @dev The balance slot of `owner` is given by:
/// ```
/// mstore(0x0c, _BALANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev The allowance slot of (`owner`, `spender`) is given by:
/// ```
/// mstore(0x20, spender)
/// mstore(0x0c, _ALLOWANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let allowanceSlot := keccak256(0x0c, 0x34)
/// ```
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
/// @dev The nonce slot of `owner` is given by:
/// ```
/// mstore(0x0c, _NONCES_SLOT_SEED)
/// mstore(0x00, owner)
/// let nonceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 private constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev `keccak256("1")`.
bytes32 private constant _VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the decimals places of the token.
function decimals() public view virtual returns (uint8) {
return 18;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_TOTAL_SUPPLY_SLOT)
}
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x34))
}
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(msg.sender, to, amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, caller())
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
}
_afterTokenTransfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if add(allowance_, 1) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-2612 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For more performance, override to return the constant value
/// of `keccak256(bytes(name()))` if `name()` will never change.
function _constantNameHash() internal view virtual returns (bytes32 result) {}
/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
// Revert if the block timestamp is greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
let m := mload(0x40) // Grab the free memory pointer.
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Prepare the domain separator.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), _VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
mstore(0x2e, keccak256(m, 0xa0))
// Prepare the struct hash.
mstore(m, _PERMIT_TYPEHASH)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
mstore(0x4e, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0x00, keccak256(0x2c, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
let t := staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Grab the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), _VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
result := keccak256(m, 0xa0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
_beforeTokenTransfer(address(0), to, amount);
/// @solidity memory-safe-assembly
assembly {
let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
let totalSupplyAfter := add(totalSupplyBefore, amount)
// Revert if the total supply overflows.
if lt(totalSupplyAfter, totalSupplyBefore) {
mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
revert(0x1c, 0x04)
}
// Store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
}
_afterTokenTransfer(address(0), to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
_beforeTokenTransfer(from, address(0), amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, from)
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Subtract and store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
_afterTokenTransfer(from, address(0), amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Moves `amount` of tokens from `from` to `to`.
function _transfer(address from, address to, uint256 amount) internal virtual {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL ALLOWANCE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if add(allowance_, 1) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
}
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
let owner_ := shl(96, owner)
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any transfer of tokens.
/// This includes minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/// @dev Hook that is called after any transfer of tokens.
/// This includes minting and burning.
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {ERC20} from "./ERC20.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
/// @notice Simple ERC4626 tokenized Vault implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC4626.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The default underlying decimals.
uint8 internal constant _DEFAULT_UNDERLYING_DECIMALS = 18;
/// @dev The default decimals offset.
uint8 internal constant _DEFAULT_DECIMALS_OFFSET = 0;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Cannot deposit more than the max limit.
error DepositMoreThanMax();
/// @dev Cannot mint more than the max limit.
error MintMoreThanMax();
/// @dev Cannot withdraw more than the max limit.
error WithdrawMoreThanMax();
/// @dev Cannot redeem more than the max limit.
error RedeemMoreThanMax();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted during a mint call or deposit call.
event Deposit(address indexed by, address indexed owner, uint256 assets, uint256 shares);
/// @dev Emitted during a withdraw call or redeem call.
event Withdraw(
address indexed by,
address indexed to,
address indexed owner,
uint256 assets,
uint256 shares
);
/// @dev `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 private constant _DEPOSIT_EVENT_SIGNATURE =
0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 private constant _WITHDRAW_EVENT_SIGNATURE =
0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC4626 CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev To be overridden to return the address of the underlying asset.
///
/// - MUST be an ERC20 token contract.
/// - MUST NOT revert.
function asset() public view virtual returns (address);
/// @dev To be overridden to return the number of decimals of the underlying asset.
/// Default: 18.
///
/// - MUST NOT revert.
function _underlyingDecimals() internal view virtual returns (uint8) {
return _DEFAULT_UNDERLYING_DECIMALS;
}
/// @dev Override to return a non-zero value to make the inflation attack even more unfeasible.
/// Only used when {_useVirtualShares} returns true.
/// Default: 0.
///
/// - MUST NOT revert.
function _decimalsOffset() internal view virtual returns (uint8) {
return _DEFAULT_DECIMALS_OFFSET;
}
/// @dev Returns whether virtual shares will be used to mitigate the inflation attack.
/// See: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706
/// Override to return true or false.
/// Default: true.
///
/// - MUST NOT revert.
function _useVirtualShares() internal view virtual returns (bool) {
return true;
}
/// @dev Returns the decimals places of the token.
///
/// - MUST NOT revert.
function decimals() public view virtual override(ERC20) returns (uint8) {
if (!_useVirtualShares()) return _underlyingDecimals();
return _underlyingDecimals() + _decimalsOffset();
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ASSET DECIMALS GETTER HELPER */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Helper function to get the decimals of the underlying asset.
/// Useful for setting the return value of `_underlyingDecimals` during initialization.
/// If the retrieval succeeds, `success` will be true, and `result` will hold the result.
/// Otherwise, `success` will be false, and `result` will be zero.
///
/// Example usage:
/// ```
/// (bool success, uint8 result) = _tryGetAssetDecimals(underlying);
/// _decimals = success ? result : _DEFAULT_UNDERLYING_DECIMALS;
/// ```
function _tryGetAssetDecimals(address underlying)
internal
view
returns (bool success, uint8 result)
{
/// @solidity memory-safe-assembly
assembly {
// Store the function selector of `decimals()`.
mstore(0x00, 0x313ce567)
// Arguments are evaluated last to first.
success :=
and(
// Returned value is less than 256, at left-padded to 32 bytes.
and(lt(mload(0x00), 0x100), gt(returndatasize(), 0x1f)),
// The staticcall succeeds.
staticcall(gas(), underlying, 0x1c, 0x04, 0x00, 0x20)
)
result := mul(mload(0x00), success)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ACCOUNTING LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the total amount of the underlying asset managed by the Vault.
///
/// - SHOULD include any compounding that occurs from the yield.
/// - MUST be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT revert.
function totalAssets() public view virtual returns (uint256 assets) {
assets = SafeTransferLib.balanceOf(asset(), address(this));
}
/// @dev Returns the amount of shares that the Vault will exchange for the amount of
/// assets provided, in an ideal scenario where all conditions are met.
///
/// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT show any variations depending on the caller.
/// - MUST NOT reflect slippage or other on-chain conditions, during the actual exchange.
/// - MUST NOT revert.
///
/// Note: This calculation MAY NOT reflect the "per-user" price-per-share, and instead
/// should reflect the "average-user's" price-per-share, i.e. what the average user should
/// expect to see when exchanging to and from.
function convertToShares(uint256 assets) public view virtual returns (uint256 shares) {
if (!_useVirtualShares()) {
uint256 supply = totalSupply();
return _eitherIsZero(assets, supply)
? _initialConvertToShares(assets)
: FixedPointMathLib.fullMulDiv(assets, supply, totalAssets());
}
uint256 o = _decimalsOffset();
if (o == uint256(0)) {
return FixedPointMathLib.fullMulDiv(assets, totalSupply() + 1, _inc(totalAssets()));
}
return FixedPointMathLib.fullMulDiv(assets, totalSupply() + 10 ** o, _inc(totalAssets()));
}
/// @dev Returns the amount of assets that the Vault will exchange for the amount of
/// shares provided, in an ideal scenario where all conditions are met.
///
/// - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
/// - MUST NOT show any variations depending on the caller.
/// - MUST NOT reflect slippage or other on-chain conditions, during the actual exchange.
/// - MUST NOT revert.
///
/// Note: This calculation MAY NOT reflect the "per-user" price-per-share, and instead
/// should reflect the "average-user's" price-per-share, i.e. what the average user should
/// expect to see when exchanging to and from.
function convertToAssets(uint256 shares) public view virtual returns (uint256 assets) {
if (!_useVirtualShares()) {
uint256 supply = totalSupply();
return supply == uint256(0)
? _initialConvertToAssets(shares)
: FixedPointMathLib.fullMulDiv(shares, totalAssets(), supply);
}
uint256 o = _decimalsOffset();
if (o == uint256(0)) {
return FixedPointMathLib.fullMulDiv(shares, totalAssets() + 1, _inc(totalSupply()));
}
return FixedPointMathLib.fullMulDiv(shares, totalAssets() + 1, totalSupply() + 10 ** o);
}
/// @dev Allows an on-chain or off-chain user to simulate the effects of their deposit
/// at the current block, given current on-chain conditions.
///
/// - MUST return as close to and no more than the exact amount of Vault shares that
/// will be minted in a deposit call in the same transaction, i.e. deposit should
/// return the same or more shares as `previewDeposit` if call in the same transaction.
/// - MUST NOT account for deposit limits like those returned from `maxDeposit` and should
/// always act as if the deposit will be accepted, regardless of approvals, etc.
/// - MUST be inclusive of deposit fees. Integrators should be aware of this.
/// - MUST not revert.
///
/// Note: Any unfavorable discrepancy between `convertToShares` and `previewDeposit` SHOULD
/// be considered slippage in share price or some other type of condition, meaning
/// the depositor will lose assets by depositing.
function previewDeposit(uint256 assets) public view virtual returns (uint256 shares) {
shares = convertToShares(assets);
}
/// @dev Allows an on-chain or off-chain user to simulate the effects of their mint
/// at the current block, given current on-chain conditions.
///
/// - MUST return as close to and no fewer than the exact amount of assets that
/// will be deposited in a mint call in the same transaction, i.e. mint should
/// return the same or fewer assets as `previewMint` if called in the same transaction.
/// - MUST NOT account for mint limits like those returned from `maxMint` and should
/// always act as if the mint will be accepted, regardless of approvals, etc.
/// - MUST be inclusive of deposit fees. Integrators should be aware of this.
/// - MUST not revert.
///
/// Note: Any unfavorable discrepancy between `convertToAssets` and `previewMint` SHOULD
/// be considered slippage in share price or some other type of condition,
/// meaning the depositor will lose assets by minting.
function previewMint(uint256 shares) public view virtual returns (uint256 assets) {
if (!_useVirtualShares()) {
uint256 supply = totalSupply();
return supply == uint256(0)
? _initialConvertToAssets(shares)
: FixedPointMathLib.fullMulDivUp(shares, totalAssets(), supply);
}
uint256 o = _decimalsOffset();
if (o == uint256(0)) {
return FixedPointMathLib.fullMulDivUp(shares, totalAssets() + 1, _inc(totalSupply()));
}
return FixedPointMathLib.fullMulDivUp(shares, totalAssets() + 1, totalSupply() + 10 ** o);
}
/// @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal
/// at the current block, given the current on-chain conditions.
///
/// - MUST return as close to and no fewer than the exact amount of Vault shares that
/// will be burned in a withdraw call in the same transaction, i.e. withdraw should
/// return the same or fewer shares as `previewWithdraw` if call in the same transaction.
/// - MUST NOT account for withdrawal limits like those returned from `maxWithdraw` and should
/// always act as if the withdrawal will be accepted, regardless of share balance, etc.
/// - MUST be inclusive of withdrawal fees. Integrators should be aware of this.
/// - MUST not revert.
///
/// Note: Any unfavorable discrepancy between `convertToShares` and `previewWithdraw` SHOULD
/// be considered slippage in share price or some other type of condition,
/// meaning the depositor will lose assets by depositing.
function previewWithdraw(uint256 assets) public view virtual returns (uint256 shares) {
if (!_useVirtualShares()) {
uint256 supply = totalSupply();
return _eitherIsZero(assets, supply)
? _initialConvertToShares(assets)
: FixedPointMathLib.fullMulDivUp(assets, supply, totalAssets());
}
uint256 o = _decimalsOffset();
if (o == uint256(0)) {
return FixedPointMathLib.fullMulDivUp(assets, totalSupply() + 1, _inc(totalAssets()));
}
return FixedPointMathLib.fullMulDivUp(assets, totalSupply() + 10 ** o, _inc(totalAssets()));
}
/// @dev Allows an on-chain or off-chain user to simulate the effects of their redemption
/// at the current block, given current on-chain conditions.
///
/// - MUST return as close to and no more than the exact amount of assets that
/// will be withdrawn in a redeem call in the same transaction, i.e. redeem should
/// return the same or more assets as `previewRedeem` if called in the same transaction.
/// - MUST NOT account for redemption limits like those returned from `maxRedeem` and should
/// always act as if the redemption will be accepted, regardless of approvals, etc.
/// - MUST be inclusive of withdrawal fees. Integrators should be aware of this.
/// - MUST NOT revert.
///
/// Note: Any unfavorable discrepancy between `convertToAssets` and `previewRedeem` SHOULD
/// be considered slippage in share price or some other type of condition,
/// meaning the depositor will lose assets by depositing.
function previewRedeem(uint256 shares) public view virtual returns (uint256 assets) {
assets = convertToAssets(shares);
}
/// @dev Private helper to return if either value is zero.
function _eitherIsZero(uint256 a, uint256 b) private pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := or(iszero(a), iszero(b))
}
}
/// @dev Private helper to return `x + 1` without the overflow check.
/// Used for computing the denominator input to `FixedPointMathLib.fullMulDiv(a, b, x + 1)`.
/// When `x == type(uint256).max`, we get `x + 1 == 0` (mod 2**256 - 1),
/// and `FixedPointMathLib.fullMulDiv` will revert as the denominator is zero.
function _inc(uint256 x) private pure returns (uint256) {
unchecked {
return x + 1;
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DEPOSIT / WITHDRAWAL LIMIT LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the maximum amount of the underlying asset that can be deposited
/// into the Vault for `to`, via a deposit call.
///
/// - MUST return a limited value if `to` is subject to some deposit limit.
/// - MUST return `2**256-1` if there is no maximum limit.
/// - MUST NOT revert.
function maxDeposit(address to) public view virtual returns (uint256 maxAssets) {
to = to; // Silence unused variable warning.
maxAssets = type(uint256).max;
}
/// @dev Returns the maximum amount of the Vault shares that can be minter for `to`,
/// via a mint call.
///
/// - MUST return a limited value if `to` is subject to some mint limit.
/// - MUST return `2**256-1` if there is no maximum limit.
/// - MUST NOT revert.
function maxMint(address to) public view virtual returns (uint256 maxShares) {
to = to; // Silence unused variable warning.
maxShares = type(uint256).max;
}
/// @dev Returns the maximum amount of the underlying asset that can be withdrawn
/// from the `owner`'s balance in the Vault, via a withdraw call.
///
/// - MUST return a limited value if `owner` is subject to some withdrawal limit or timelock.
/// - MUST NOT revert.
function maxWithdraw(address owner) public view virtual returns (uint256 maxAssets) {
maxAssets = convertToAssets(balanceOf(owner));
}
/// @dev Returns the maximum amount of Vault shares that can be redeemed
/// from the `owner`'s balance in the Vault, via a redeem call.
///
/// - MUST return a limited value if `owner` is subject to some withdrawal limit or timelock.
/// - MUST return `balanceOf(owner)` otherwise.
/// - MUST NOT revert.
function maxRedeem(address owner) public view virtual returns (uint256 maxShares) {
maxShares = balanceOf(owner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DEPOSIT / WITHDRAWAL LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints `shares` Vault shares to `to` by depositing exactly `assets`
/// of underlying tokens.
///
/// - MUST emit the {Deposit} event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault
/// contract before the deposit execution, and are accounted for during deposit.
/// - MUST revert if all of `assets` cannot be deposited, such as due to deposit limit,
/// slippage, insufficient approval, etc.
///
/// Note: Most implementations will require pre-approval of the Vault with the
/// Vault's underlying `asset` token.
function deposit(uint256 assets, address to) public virtual returns (uint256 shares) {
if (assets > maxDeposit(to)) _revert(0xb3c61a83); // `DepositMoreThanMax()`.
shares = previewDeposit(assets);
_deposit(msg.sender, to, assets, shares);
}
/// @dev Mints exactly `shares` Vault shares to `to` by depositing `assets`
/// of underlying tokens.
///
/// - MUST emit the {Deposit} event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault
/// contract before the mint execution, and are accounted for during mint.
/// - MUST revert if all of `shares` cannot be deposited, such as due to deposit limit,
/// slippage, insufficient approval, etc.
///
/// Note: Most implementations will require pre-approval of the Vault with the
/// Vault's underlying `asset` token.
function mint(uint256 shares, address to) public virtual returns (uint256 assets) {
if (shares > maxMint(to)) _revert(0x6a695959); // `MintMoreThanMax()`.
assets = previewMint(shares);
_deposit(msg.sender, to, assets, shares);
}
/// @dev Burns `shares` from `owner` and sends exactly `assets` of underlying tokens to `to`.
///
/// - MUST emit the {Withdraw} event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault
/// contract before the withdraw execution, and are accounted for during withdraw.
/// - MUST revert if all of `assets` cannot be withdrawn, such as due to withdrawal limit,
/// slippage, insufficient balance, etc.
///
/// Note: Some implementations will require pre-requesting to the Vault before a withdrawal
/// may be performed. Those methods should be performed separately.
function withdraw(uint256 assets, address to, address owner)
public
virtual
returns (uint256 shares)
{
if (assets > maxWithdraw(owner)) _revert(0x936941fc); // `WithdrawMoreThanMax()`.
shares = previewWithdraw(assets);
_withdraw(msg.sender, to, owner, assets, shares);
}
/// @dev Burns exactly `shares` from `owner` and sends `assets` of underlying tokens to `to`.
///
/// - MUST emit the {Withdraw} event.
/// - MAY support an additional flow in which the underlying tokens are owned by the Vault
/// contract before the redeem execution, and are accounted for during redeem.
/// - MUST revert if all of shares cannot be redeemed, such as due to withdrawal limit,
/// slippage, insufficient balance, etc.
///
/// Note: Some implementations will require pre-requesting to the Vault before a redeem
/// may be performed. Those methods should be performed separately.
function redeem(uint256 shares, address to, address owner)
public
virtual
returns (uint256 assets)
{
if (shares > maxRedeem(owner)) _revert(0x4656425a); // `RedeemMoreThanMax()`.
assets = previewRedeem(shares);
_withdraw(msg.sender, to, owner, assets, shares);
}
/// @dev Internal helper for reverting efficiently.
function _revert(uint256 s) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, s)
revert(0x1c, 0x04)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For deposits and mints.
///
/// Emits a {Deposit} event.
function _deposit(address by, address to, uint256 assets, uint256 shares) internal virtual {
SafeTransferLib.safeTransferFrom(asset(), by, address(this), assets);
_mint(to, shares);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Deposit} event.
mstore(0x00, assets)
mstore(0x20, shares)
let m := shr(96, not(0))
log3(0x00, 0x40, _DEPOSIT_EVENT_SIGNATURE, and(m, by), and(m, to))
}
_afterDeposit(assets, shares);
}
/// @dev For withdrawals and redemptions.
///
/// Emits a {Withdraw} event.
function _withdraw(address by, address to, address owner, uint256 assets, uint256 shares)
internal
virtual
{
if (by != owner) _spendAllowance(owner, by, shares);
_beforeWithdraw(assets, shares);
_burn(owner, shares);
SafeTransferLib.safeTransfer(asset(), to, assets);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Withdraw} event.
mstore(0x00, assets)
mstore(0x20, shares)
let m := shr(96, not(0))
log4(0x00, 0x40, _WITHDRAW_EVENT_SIGNATURE, and(m, by), and(m, to), and(m, owner))
}
}
/// @dev Internal conversion function (from assets to shares) to apply when the Vault is empty.
/// Only used when {_useVirtualShares} returns false.
///
/// Note: Make sure to keep this function consistent with {_initialConvertToAssets}
/// when overriding it.
function _initialConvertToShares(uint256 assets)
internal
view
virtual
returns (uint256 shares)
{
shares = assets;
}
/// @dev Internal conversion function (from shares to assets) to apply when the Vault is empty.
/// Only used when {_useVirtualShares} returns false.
///
/// Note: Make sure to keep this function consistent with {_initialConvertToShares}
/// when overriding it.
function _initialConvertToAssets(uint256 shares)
internal
view
virtual
returns (uint256 assets)
{
assets = shares;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any withdrawal or redemption.
function _beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
/// @dev Hook that is called after any deposit or mint.
function _afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { ERC7540Lib, ERC7540_FilledRequest, ERC7540_Request } from "../types/ERC7540Types.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
/// @notice Simple ERC7540 async Tokenized Vault implementation
/// @author Solthodox (https://github.com/Solthodox)
abstract contract ERC7540 is ERC4626 {
using SafeTransferLib for address;
using ERC7540Lib for ERC7540_Request;
using ERC7540Lib for ERC7540_FilledRequest;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `assets` tokens are deposited into the vault
event DepositRequest(
address indexed controller, address indexed owner, uint256 indexed requestId, address source, uint256 assets
);
/// @dev Emitted when `shares` vault shares are redeemed
event RedeemRequest(
address indexed controller, address indexed owner, uint256 indexed requestId, address source, uint256 shares
);
/// @dev Emitted when a deposit request is fulfilled after being processed
event FulfillDepositRequest(address indexed controller, uint256 assets, uint256 shares);
/// @dev Emitted when a redeem request is fulfilled after being processed
event FulfillRedeemRequest(address indexed controller, uint256 shares, uint256 assets);
/// @dev Emitted when `controller` gives allowance to `operator`
event OperatorSet(address indexed controller, address indexed operator, bool approved);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Thrown when an unauthorized address attempts to act as a controller
error InvalidController();
/// @notice Thrown when trying to deposit or interact with zero assets
error InvalidZeroAssets();
/// @notice Thrown when trying to redeem or interact with zero shares
error InvalidZeroShares();
/// @notice Thrown when trying to set an invalid operator, such as setting oneself as an operator
error InvalidOperator();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Saves the ERC7540 deposit requests when calling `requestDeposit`
mapping(address => ERC7540_Request) internal _pendingDepositRequest;
/// @notice Saves the ERC7540 redeem requests when calling `requestRedeem`
mapping(address => ERC7540_Request) internal _pendingRedeemRequest;
/// @notice Saves the result of the deposit after the request has been processed
mapping(address => ERC7540_FilledRequest) internal _claimableDepositRequest;
/// @notice Saves the result of the redeem after the request has been processed
mapping(address => ERC7540_FilledRequest) internal _claimableRedeemRequest;
/// @notice ERC7540 operator approvals
mapping(address controller => mapping(address operator => bool)) public isOperator;
/// @dev Preview functions for ERC-7540 vaults revert
function previewDeposit(uint256 assets) public pure override returns (uint256 shares) {
assets; // silence compiler warnings
shares; // silence compiler warnings
revert();
}
/// @dev Preview functions for ERC-7540 vaults revert
function previewMint(uint256 shares) public pure override returns (uint256 assets) {
shares; // silence compiler warnings
assets; // silence compiler warnings
revert();
}
/// @dev Preview functions for ERC-7540 vaults revert
function previewWithdraw(uint256 assets) public pure override returns (uint256 shares) {
assets; // silence compiler warnings
shares; // silence compiler warnings
revert();
}
/// @dev Preview functions for ERC-7540 vaults revert
function previewRedeem(uint256 shares) public pure override returns (uint256 assets) {
shares; // silence compiler warnings
assets; // silence compiler warnings
revert();
}
/// @dev The deposit amount is limited by the claimable deposit requests of the user
function maxDeposit(address to) public view virtual override returns (uint256 assets) {
return _claimableDepositRequest[to].assets;
}
/// @dev The mint amount is limited by the claimable deposit requests of the user
function maxMint(address to) public view virtual override returns (uint256 shares) {
return convertToShares(maxDeposit(to));
}
/// @dev The withdraw amount is limited by the claimable redeem requests of the user
function maxWithdraw(address owner) public view virtual override returns (uint256 assets) {
return convertToAssets(maxRedeem(owner));
}
/// @dev The redeem amount is limited by the claimable redeem requests of the user
function maxRedeem(address owner) public view virtual override returns (uint256 shares) {
return _claimableRedeemRequest[owner].shares;
}
/// @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit.
///
/// - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow.
/// - MUST revert if all of assets cannot be requested for deposit.
/// - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller,
/// approval of ERC-20 tokens from owner to sender is NOT enough.
///
/// @param assets the amount of deposit assets to transfer from owner
/// @param controller the controller of the request who will be able to operate the request
/// @param owner the source of the deposit assets
///
/// NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token.
function requestDeposit(
uint256 assets,
address controller,
address owner
)
public
virtual
returns (uint256 requestId)
{
if (assets == 0) revert InvalidZeroAssets();
requestId = _requestDeposit(assets, controller, owner, msg.sender);
}
/// @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem.
///
/// - MUST support a redeem Request flow where the control of shares is taken from sender directly
/// where msg.sender has ERC-20 approval over the shares of owner.
/// - MUST revert if all of shares cannot be requested for redeem.
///
/// @param shares the amount of shares to be redeemed to transfer from owner
/// @param controller the controller of the request who will be able to operate the request
/// @param owner the source of the shares to be redeemed
///
/// NOTE: most implementations will require pre-approval of the Vault with the Vault's share token.
function requestRedeem(
uint256 shares,
address controller,
address owner
)
public
virtual
returns (uint256 requestId)
{
if (shares == 0) revert InvalidZeroShares();
// If msg.sender is operator of owner, the transfer is executed as if
// the sender is the owner, to bypass the allowance check
address sender = isOperator[owner][msg.sender] ? owner : msg.sender;
return _requestRedeem(shares, controller, sender, msg.sender);
}
/// @dev Mints shares Vault shares to receiver by claiming the Request of the controller.
///
/// - MUST emit the Deposit event.
/// - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator.
function deposit(uint256 assets, address receiver) public virtual override returns (uint256 shares) {
return deposit(assets, receiver, msg.sender);
}
/// @dev Mints shares Vault shares to receiver by claiming the Request of the controller.
///
/// - MUST emit the Deposit event.
/// - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator.
function deposit(uint256 assets, address receiver, address controller) public virtual returns (uint256 shares) {
_validateController(controller);
if (assets > maxDeposit(controller)) revert DepositMoreThanMax();
ERC7540_FilledRequest memory claimable = _claimableDepositRequest[controller];
shares = claimable.convertToSharesUp(assets);
(shares,) = _deposit(assets, shares, receiver, controller);
}
/// @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller.
///
/// - MUST emit the Deposit event.
/// - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator.
function mint(uint256 shares, address receiver) public virtual override returns (uint256 assets) {
return mint(shares, receiver, msg.sender);
}
/// @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller.
///
/// - MUST emit the Deposit event.
/// - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator.
function mint(uint256 shares, address receiver, address controller) public virtual returns (uint256 assets) {
_validateController(controller);
if (shares > maxMint(controller)) revert MintMoreThanMax();
ERC7540_FilledRequest memory claimable = _claimableDepositRequest[controller];
assets = claimable.convertToAssetsUp(shares);
(, assets) = _deposit(assets, shares, receiver, controller);
}
/// @notice Claims processed redemption request
/// @dev Can only be called by controller or approved operator
/// @param shares Amount of shares to redeem
/// @param to Address to receive the assets
/// @param controller Controller of the redemption request
/// @return assets Amount of assets returned
function redeem(uint256 shares, address to, address controller) public virtual override returns (uint256 assets) {
if (shares > maxRedeem(controller)) revert RedeemMoreThanMax();
_validateController(controller);
ERC7540_FilledRequest memory claimable = _claimableRedeemRequest[controller];
assets = claimable.convertToAssets(shares);
(assets,) = _withdraw(assets, shares, to, controller);
}
/// @notice Claims processed redemption request for exact assets
/// @dev Can only be called by controller or approved operator
/// @param assets Exact amount of assets to withdraw
/// @param to Address to receive the assets
/// @param controller Controller of the redemption request
/// @return shares Amount of shares burned
function withdraw(
uint256 assets,
address to,
address controller
)
public
virtual
override
returns (uint256 shares)
{
if (assets > maxWithdraw(controller)) revert WithdrawMoreThanMax();
_validateController(controller);
ERC7540_FilledRequest memory claimable = _claimableRedeemRequest[controller];
shares = claimable.convertToSharesUp(assets);
(, shares) = _withdraw(assets, shares, to, controller);
}
/// @notice Returns the pending redemption request amount for a controller
/// @param controller Address to check pending redemption for
/// @return Amount of shares pending redemption
function pendingRedeemRequest(address controller) public view virtual returns (uint256) {
return _pendingRedeemRequest[controller].unwrap();
}
/// @notice Returns the pending deposit request amount for a controller
/// @param controller Address to check pending deposit for
/// @return Amount of assets pending deposit
function pendingDepositRequest(address controller) public view virtual returns (uint256) {
return _pendingDepositRequest[controller].unwrap();
}
/// @notice Returns the claimable deposit amount for a controller
/// @param controller Address to check claimable deposit for
/// @return Amount of assets available to claim
function claimableDepositRequest(address controller) public view virtual returns (uint256) {
return maxDeposit(controller);
}
/// @notice Returns the claimable redemption amount for a controller
/// @param controller Address to check claimable redemption for
/// @return Amount of shares available to claim
function claimableRedeemRequest(address controller) public view virtual returns (uint256) {
return maxRedeem(controller);
}
function _deposit(
uint256 assets,
uint256 shares,
address receiver,
address controller
)
internal
virtual
returns (uint256 sharesReturn, uint256 assetsReturn)
{
unchecked {
_claimableDepositRequest[controller].assets -= assets;
_claimableDepositRequest[controller].shares -= shares;
}
_mint(receiver, shares);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Deposit} event.
mstore(0x00, assets)
mstore(0x20, shares)
let m := shr(96, not(0))
log3(
0x00,
0x40,
0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7,
and(m, controller),
and(m, receiver)
)
}
return (shares, assets);
}
function _withdraw(
uint256 assets,
uint256 shares,
address receiver,
address controller
)
internal
virtual
returns (uint256 assetsReturn, uint256 sharesReturn)
{
unchecked {
_claimableRedeemRequest[controller].assets -= assets;
_claimableRedeemRequest[controller].shares -= shares;
}
asset().safeTransfer(receiver, assets);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Withdraw} event.
mstore(0x00, assets)
mstore(0x20, shares)
let m := shr(96, not(0))
log4(
0x00,
0x40,
0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db,
and(m, controller),
and(m, receiver),
and(m, controller)
)
}
return (assets, shares);
}
function _requestDeposit(
uint256 assets,
address controller,
address owner,
address source
)
internal
virtual
returns (uint256 requestId)
{
source;
asset().safeTransferFrom(source, address(this), assets);
_pendingDepositRequest[controller] = _pendingDepositRequest[controller].add(assets);
emit DepositRequest(controller, owner, requestId, source, assets);
return 0;
}
function _requestRedeem(
uint256 shares,
address controller,
address owner,
address source
)
internal
virtual
returns (uint256 requestId)
{
source;
_transfer(owner, address(this), shares);
_pendingRedeemRequest[controller] = _pendingRedeemRequest[controller].add(shares);
emit RedeemRequest(controller, owner, requestId, source, shares);
return 0;
}
/// @dev Sets or removes an operator for the caller.
///
/// @param operator The address of the operator.
/// @param approved The approval status.
/// @return success Whether the call was executed successfully or not
function setOperator(address operator, bool approved) public returns (bool success) {
if (msg.sender == operator) revert InvalidOperator();
isOperator[msg.sender][operator] = approved;
emit OperatorSet(msg.sender, operator, approved);
return true;
}
/// @dev Performs operator and controller permission checks
function _validateController(address controller) private view {
if (msg.sender != controller && !isOperator[controller][msg.sender]) revert InvalidController();
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called when processing a deposit request and make it claimable.
function _fulfillDepositRequest(
address controller,
uint256 assetsFulfilled,
uint256 sharesMinted
)
internal
virtual
{
_pendingDepositRequest[controller] = _pendingDepositRequest[controller].sub(assetsFulfilled);
_claimableDepositRequest[controller].assets += assetsFulfilled;
_claimableDepositRequest[controller].shares += sharesMinted;
emit FulfillDepositRequest(controller, assetsFulfilled, sharesMinted);
}
/// @dev Hook that is called when processing a redeem request and make it claimable.
/// @dev It assumes user transferred its shares to the contract when requesting a redeem
function _fulfillRedeemRequest(
uint256 sharesFulfilled,
uint256 assetsWithdrawn,
address controller,
bool strict
)
internal
virtual
{
if (strict) {
_pendingRedeemRequest[controller] = _pendingRedeemRequest[controller].sub(sharesFulfilled);
} else {
_pendingRedeemRequest[controller] = _pendingRedeemRequest[controller].sub0(sharesFulfilled);
}
_claimableRedeemRequest[controller].assets += assetsWithdrawn;
_claimableRedeemRequest[controller].shares += sharesFulfilled;
emit FulfillRedeemRequest(controller, sharesFulfilled, assetsWithdrawn);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ERC7540ProcessRedeemBase } from "./common/ERC7540ProcessRedeemBase.sol";
import { ProcessRedeemRequestParams } from "types/Lib.sol";
/// @title ERC7540Engine
/// @notice Implementation of a ERC4626 multi-vault deposit liquidity engine with cross-chain functionalities
/// @dev Extends ERC7540ProcessRedeemBase contract and implements advanced redeem request processing
contract ERC7540Engine is ERC7540ProcessRedeemBase {
/// @dev Emitted when a redeem request is fulfilled after being processed
event FulfillSettledRequest(address indexed controller, uint256 shares, uint256 assets);
/// @notice Processes a redemption request for a given controller
/// @dev This function is restricted to the RELAYER_ROLE and handles asynchronous processing of redemption requests,
/// including cross-chain withdrawals
/// @param params redeem request parameters
function processRedeemRequest(ProcessRedeemRequestParams calldata params)
external
payable
onlyRoles(RELAYER_ROLE)
nonReentrant
{
// Retrieve the pending redeem request for the specified controller
// This request may involve cross-chain withdrawals from various ERC4626 vaults
// Process the redemption request asynchronously
// Parameters:
// 1. pendingRedeemRequest(controller): Fetches the pending shares
// 2. controller: The address initiating the redemption (used as both 'from' and 'to')
_processRedeemRequest(
ProcessRedeemRequestConfig(
params.shares == 0 ? pendingRedeemRequest(params.controller) : params.shares,
params.controller,
params.sXsV,
params.sXmV,
params.mXsV,
params.mXmV
)
);
// Note: After processing, the redeemed assets are held by this contract
// The user can later claim these assets using `redeem` or `withdraw`
}
/// @notice Fulfills a settled cross-chain redemption request
/// @dev Called by the gateway contract when cross-chain assets have been received.
/// Converts the requested assets to shares and fulfills the redemption request.
/// Only callable by the gateway contract.
/// @param controller The address that initiated the redemption request
/// @param requestedAssets The original amount of assets requested
/// @param fulfilledAssets The actual amount of assets received after bridging
function fulfillSettledRequest(address controller, uint256 requestedAssets, uint256 fulfilledAssets) public {
if (msg.sender != address(gateway)) revert Unauthorized();
uint256 shares = convertToShares(requestedAssets);
pendingProcessedShares[controller] = _sub0(pendingProcessedShares[controller], shares);
_fulfillRedeemRequest(shares, fulfilledAssets, controller, false);
emit FulfillSettledRequest(controller, shares, fulfilledAssets);
}
/// @dev Helper function to fetch module function selectors
function selectors() public pure returns (bytes4[] memory) {
bytes4[] memory s = new bytes4[](3);
s[0] = this.processRedeemRequest.selector;
s[1] = this.fulfillSettledRequest.selector;
s[2] = this.setDustThreshold.selector;
return s;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ModuleBase } from "common/Lib.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol";
import { VaultData, VaultLib } from "types/Lib.sol";
/// @title ERC7540EngineBase
/// @notice Base contract for ERC7540 engine that manages multi-vault deposits and withdrawals across chains
/// @dev Extends ModuleBase to provide core functionality for processing redemption requests and managing vault state
contract ERC7540EngineBase is ModuleBase {
/// @dev Library for vault-related operations
using VaultLib for VaultData;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Internal cache struct to allocate in memory
struct ProcessRedeemRequestCache {
// List of vauts to withdraw from on each chain
uint256[WITHDRAWAL_QUEUE_SIZE][N_CHAINS] dstVaults;
// List of shares to redeem on each vault in each chain
uint256[WITHDRAWAL_QUEUE_SIZE][N_CHAINS] sharesPerVault;
// List of assets to withdraw on each vault in each chain
uint256[WITHDRAWAL_QUEUE_SIZE][N_CHAINS] assetsPerVault;
// Cache length of list of each chain
uint256[N_CHAINS] lens;
// Assets to divest from other vaults
uint256 amountToWithdraw;
// Shares actually used
uint256 sharesFulfilled;
// Save assets that were withdrawn instantly
uint256 totalClaimableWithdraw;
// Cache totalAssets
uint256 totalAssets;
// Cache totalIdle
uint256 totalIdle;
// Cache totalDebt
uint256 totalDebt;
// Convert shares to assets at current price
uint256 assets;
// Cache shares to redeem
uint256 shares;
// Useful cache value for multichain withdrawals
uint256 lastIndex;
// Whether is a single or multivault withdrawal
bool isSingleChain;
bool isMultiChain;
// Whether is a single or multivault withdrawal
bool isMultiVault;
}
/// @dev Precomputes the withdrawal route following the order of the withdrawal queue
/// according to the needed assets
/// @param cache the memory pointer of the cache
/// @dev writes the route to the cache struct
///
/// Note: First it will try to fulfill the request with idle assets, after that it will
/// loop through the withdrawal queue and compute the destination chains and vaults on each
/// destionation chain, plus the shaes to redeem on each vault
function _prepareWithdrawalRoute(ProcessRedeemRequestCache memory cache, bool despiseDust) internal view {
// Use the local vaults first
_exhaustWithdrawalQueue(cache, localWithdrawalQueue, false, false);
// Use the crosschain vaults after
_exhaustWithdrawalQueue(cache, xChainWithdrawalQueue, true, despiseDust);
}
/// @notice Internal function to process a withdrawal queue and determine optimal withdrawal routes
/// @dev Iterates through a withdrawal queue to calculate how to fulfill a withdrawal request across multiple vaults
/// and chains.
/// The function:
/// 1. Processes vaults in queue order until request is fulfilled
/// 2. Calculates shares to withdraw from each vault
/// 3. Updates debt tracking
/// 4. Determines if withdrawal is single/multi chain and single/multi vault
/// 5. Maintains withdrawal state in the cache structure
///
/// For each vault in the queue:
/// - Checks maximum withdrawable amount
/// - Calculates required shares
/// - Updates chain-specific withdrawal arrays
/// - Tracks debt reductions
/// - Updates withdrawal type flags (single/multi chain/vault)
///
/// @param cache Storage structure containing withdrawal state and routing information:
/// - dstVaults: Arrays of vault IDs per chain
/// - sharesPerVault: Shares to withdraw per vault per chain
/// - assetsPerVault: Assets to withdraw per vault per chain
/// - lens: Number of vaults to process per chain
/// - amountToWithdraw: Remaining assets to withdraw
/// - totalDebt: Running total of vault debt
/// - isSingleChain/isMultiChain/isMultiVault: Withdrawal type flags
/// @param queue The withdrawal queue to process (either local or cross-chain)
/// @param resetValues If true, resets amountToWithdraw when queue is exhausted
function _exhaustWithdrawalQueue(
ProcessRedeemRequestCache memory cache,
uint256[WITHDRAWAL_QUEUE_SIZE] memory queue,
bool resetValues,
bool despiseDust
)
internal
view
{
// Cache how many chains we need and how many vaults in each chain
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE; i++) {
// If we exhausted the queue stop
if (queue[i] == 0) {
if (resetValues) {
// reset values
cache.amountToWithdraw = cache.assets - cache.totalIdle;
}
break;
}
// If its crosschain and the dust threshold is reached stop
if (resetValues && despiseDust && cache.amountToWithdraw < dustThreshold) {
uint256 amountToWithdraw = cache.amountToWithdraw;
cache.amountToWithdraw = 0;
cache.assets -= amountToWithdraw;
cache.shares -= convertToShares(amountToWithdraw);
break;
}
// If its fulfilled stop
if (cache.amountToWithdraw == 0) {
break;
}
// Cache next vault from the withdrawal queue
VaultData memory vault = vaults[queue[i]];
// Calcualate the maxWithdraw of the vault
uint256 maxWithdraw = vault.convertToAssets(_sharesBalance(vault), asset(), true);
// Dont withdraw more than max
uint256 withdrawAssets = Math.min(maxWithdraw, cache.amountToWithdraw);
if (withdrawAssets == 0) continue;
// Cache chain index
uint256 chainIndex = chainIndexes[vault.chainId];
// Cache chain length
uint256 len = cache.lens[chainIndex];
// Push the superformId to the last index of the array
cache.dstVaults[chainIndex][len] = vault.superformId;
uint256 shares;
if (cache.amountToWithdraw >= maxWithdraw) {
uint256 balance = _sharesBalance(vault);
shares = balance;
} else {
shares = vault.convertToShares(withdrawAssets, asset(), true);
}
if (shares == 0) continue;
// Push the shares to redeeem of that vault
cache.sharesPerVault[chainIndex][len] = shares;
// Push the assetse to withdraw of that vault
cache.assetsPerVault[chainIndex][len] = withdrawAssets;
// Reduce the total debt by no more than the debt of this vault
uint256 debtReduction = Math.min(vault.totalDebt, withdrawAssets);
// Reduce totalDebt
cache.totalDebt -= debtReduction;
// Reduce needed assets
cache.amountToWithdraw -= withdrawAssets;
// Cache whether withdrawal spans multiple chains
if (vault.chainId != THIS_CHAIN_ID) {
if (!cache.isSingleChain && !cache.isMultiChain) {
// First external chain encountered
cache.isSingleChain = true;
} else if (cache.isSingleChain) {
// Find the first external chain ID
uint256 firstChainId;
for (uint256 j = 0; j < N_CHAINS; j++) {
if (cache.lens[j] > 0 && j != chainIndexes[THIS_CHAIN_ID]) {
firstChainId = j;
break;
}
}
// If this vault is from a different chain than the first one, it's multi-chain
if (chainIndex != firstChainId) {
cache.isSingleChain = false;
cache.isMultiChain = true;
}
}
// Check if there are multiple vaults in this chain
if (cache.lens[chainIndex] >= 1) {
cache.isMultiVault = true;
}
}
// Increase index for iteration
unchecked {
cache.lens[chainIndex]++;
}
}
}
/// @notice Sets the minimum amount of assets that can be withdrawn without incurring a penalty.
/// @dev This function allows the admin to define a threshold for "dust" withdrawals, which are
/// amounts considered too small to be efficiently processed. Setting this threshold helps in
/// managing the overall efficiency of asset management.
/// @param _dustThreshold The new dust threshold value to be set.
function setDustThreshold(uint256 _dustThreshold) external onlyRoles(ADMIN_ROLE) {
dustThreshold = _dustThreshold;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ERC7540EngineBase } from "./common/ERC7540EngineBase.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol";
import {
LiqRequest,
MultiDstMultiVaultStateReq,
MultiDstSingleVaultStateReq,
MultiVaultSFData,
MultiXChainMultiVaultWithdraw,
MultiXChainSingleVaultWithdraw,
ProcessRedeemRequestParams,
SingleDirectMultiVaultStateReq,
SingleDirectSingleVaultStateReq,
SingleVaultSFData,
SingleXChainMultiVaultStateReq,
SingleXChainMultiVaultWithdraw,
SingleXChainSingleVaultStateReq,
SingleXChainSingleVaultWithdraw,
VaultData,
VaultLib
} from "types/Lib.sol";
/// @title ERC7540EngineReader
/// @notice This module is used to read the state of the ERC7540Engine and to preview the withdrawal route
contract ERC7540EngineReader is ERC7540EngineBase {
/// @notice Thrown when attempting to withdraw more assets than are currently available
error InsufficientAvailableAssets();
/// @dev Library for vault-related operations
using VaultLib for VaultData;
/// @notice Simulates a withdrawal route to help relayers determine how to fulfill redemption requests
/// @dev This is an off-chain helper function that calculates the optimal route for processing
/// withdrawals across different chains and vaults. It computes:
/// 1. How much can be fulfilled directly from idle assets
/// 2. Which vaults need to be accessed for the remaining amount
/// 3. The distribution of withdrawals across different chains and vaults
/// The function follows the same withdrawal queue priority as actual withdrawals:
/// - First uses idle assets
/// - Then local chain vaults
/// - Finally cross-chain vaults
/// @param controller Address of shares owner
/// @return cachedRoute A struct containing:
/// - The withdrawal route across different chains
/// - The shares to be redeemed from each vault
/// - The assets expected from each withdrawal
/// - The amount that can be fulfilled immediately from idle assets
/// - Various cached state values needed for processing
function previewWithdrawalRoute(
address controller,
uint256 shares,
bool despiseDust
)
public
view
returns (ProcessRedeemRequestCache memory cachedRoute)
{
if (shares == 0) {
shares = pendingRedeemRequest(controller);
}
cachedRoute.shares = shares;
cachedRoute.assets = convertToAssets(shares);
cachedRoute.totalIdle = _totalIdle;
cachedRoute.totalDebt = _totalDebt;
cachedRoute.totalAssets = totalAssets();
// Cannot process more assets than the available
if (cachedRoute.assets > totalWithdrawableAssets()) {
revert InsufficientAvailableAssets();
}
// If totalIdle can covers the amount fulfill directly
if (cachedRoute.totalIdle >= cachedRoute.assets) {
cachedRoute.sharesFulfilled = shares;
cachedRoute.totalClaimableWithdraw = cachedRoute.assets;
}
// Otherwise perform Superform withdrawals
else {
// Cache amount to withdraw before reducing totalIdle
cachedRoute.amountToWithdraw = cachedRoute.assets - cachedRoute.totalIdle;
// Use totalIdle to fulfill the request
if (cachedRoute.totalIdle > 0) {
cachedRoute.totalClaimableWithdraw = cachedRoute.totalIdle;
cachedRoute.sharesFulfilled = _convertToShares(cachedRoute.totalIdle, cachedRoute.totalAssets);
}
///////////////////////////////// PREVIOUS CALCULATIONS ////////////////////////////////
_prepareWithdrawalRoute(cachedRoute, despiseDust);
}
return cachedRoute;
}
/// @dev Helper function to fetch module function selectors
function selectors() public pure returns (bytes4[] memory) {
bytes4[] memory s = new bytes4[](1);
s[0] = this.previewWithdrawalRoute.selector;
return s;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ERC7540ProcessRedeemBase } from "./common/ERC7540ProcessRedeemBase.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { ProcessRedeemRequestParams } from "types/Lib.sol";
/// @title ERC7540EngineSignatures
/// @notice Implementation of a ERC4626 multi-vault deposit liquidity engine with cross-chain functionalities
/// @dev Extends ERC7540ProcessRedeemBase contract and implements advanced redeem request processing
contract ERC7540EngineSignatures is ERC7540ProcessRedeemBase {
/// @notice Thrown when signature has expired
error SignatureExpired();
/// @notice Thrown when signature verification fails
error InvalidSignature();
/// @notice Thrown when nonce is invalid
error InvalidNonce();
/// @notice Verifies that a signature is valid for the given request parameters
/// @param params The request parameters to verify
/// @param deadline The timestamp after which the signature is no longer valid
/// @param nonce The user's current nonce
/// @param v The recovery byte of the signature
/// @param r The r value of the signature
/// @param s The s value of the signature
function verifySignature(
ProcessRedeemRequestParams calldata params,
uint256 deadline,
uint256 nonce,
uint8 v,
bytes32 r,
bytes32 s
)
public
view
returns (bool)
{
// Check deadline
if (block.timestamp > deadline) {
revert SignatureExpired();
}
// Check nonce
if (nonce != nonces(params.controller)) {
revert InvalidNonce();
}
// Hash the parameters including deadline and nonce
bytes32 paramsHash = computeHash(params, deadline, nonce);
// Verify signature using SignatureCheckerLib
return SignatureCheckerLib.isValidSignatureNow(signerRelayer, paramsHash, abi.encodePacked(r, s, v));
}
/// @notice Computes the hash of the request parameters
/// @param params The request parameters
/// @param deadline The timestamp after which the signature is no longer valid
/// @param nonce The user's current nonce
/// @return The computed hash
function computeHash(
ProcessRedeemRequestParams calldata params,
uint256 deadline,
uint256 nonce
)
public
pure
returns (bytes32)
{
return keccak256(
abi.encode(
params.controller, params.shares, params.sXsV, params.sXmV, params.mXsV, params.mXmV, deadline, nonce
)
);
}
/// @notice Process a request with a valid relayer signature
/// @param params The request parameters
/// @param deadline The timestamp after which the signature is no longer valid
/// @param v The recovery byte of the signature
/// @param r The r value of the signature
/// @param s The s value of the signature
function processSignedRequest(
ProcessRedeemRequestParams calldata params,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external
{
address controller = params.controller;
// Get and increment nonce
uint256 nonce = nonces(controller);
// Verify signature
if (!verifySignature(params, deadline, nonce, v, r, s)) {
revert InvalidSignature();
}
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, controller)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Increment and store the updated nonce
sstore(nonceSlot, add(nonceValue, 1))
}
// Process the request
_processRedeemRequest(
ProcessRedeemRequestConfig(
params.shares == 0 ? pendingRedeemRequest(params.controller) : params.shares,
params.controller,
params.sXsV,
params.sXmV,
params.mXsV,
params.mXmV
)
);
}
/// @dev Helper function to fetch module function selectors
function selectors() public pure returns (bytes4[] memory) {
bytes4[] memory s = new bytes4[](3);
s[0] = this.processSignedRequest.selector;
s[1] = this.verifySignature.selector;
s[2] = this.computeHash.selector;
return s;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ERC7540EngineBase } from "./ERC7540EngineBase.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol";
import { SafeCastLib } from "solady/utils/SafeCastLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import {
LiqRequest,
MultiVaultSFData,
MultiXChainMultiVaultWithdraw,
MultiXChainSingleVaultWithdraw,
SingleVaultSFData,
SingleXChainMultiVaultWithdraw,
SingleXChainSingleVaultWithdraw,
VaultData
} from "types/Lib.sol";
/// @title ERC7540ProcessRedeemBase
/// @notice Shared implementation for ERC7540Engine and ERC7540EngineSignatures
/// @dev Contains the core implementation of process redeem request logic
abstract contract ERC7540ProcessRedeemBase is ERC7540EngineBase {
/// @notice Thrown when attempting to withdraw more assets than are currently available
error InsufficientAvailableAssets();
/// @notice Thrown when there are not enough assets to fulfill a request
error InsufficientAssets();
/// @notice Thrown when shares requested are greater than pending redeem request
error ExcessiveSharesRequested();
/// @notice Thrown when shares are already being processed crosschain
error SharesInProcess();
/// @notice Thrown when assets were not liquidated
error AssetsNotLiquidated();
/// @dev Emitted when a redeem request is processed
event ProcessRedeemRequest(address indexed controller, uint256 shares);
/// @dev Safe casting operations for uint
using SafeCastLib for uint256;
/// @dev Safe transfer operations for ERC20 tokens
using SafeTransferLib for address;
/// @param shares to redeem and burn
/// @param controller controller that created the request
struct ProcessRedeemRequestConfig {
uint256 shares;
address controller;
SingleXChainSingleVaultWithdraw sXsV;
SingleXChainMultiVaultWithdraw sXmV;
MultiXChainSingleVaultWithdraw mXsV;
MultiXChainMultiVaultWithdraw mXmV;
}
/// @notice Executes the redeem request for a controller
/// @dev Processes a redemption request by withdrawing assets from vaults based on the withdrawal route
/// @param config The configuration for the redemption request
function _processRedeemRequest(ProcessRedeemRequestConfig memory config) internal {
// Use struct to avoid stack too deep
ProcessRedeemRequestCache memory cache;
cache.totalIdle = _totalIdle;
cache.totalDebt = _totalDebt;
// Custom error check for shares greater than pending redeem request
uint256 pendingShares = pendingRedeemRequest(config.controller);
if (config.shares > pendingShares) revert ExcessiveSharesRequested();
if (config.shares > pendingShares - pendingProcessedShares[config.controller]) revert SharesInProcess();
cache.assets = convertToAssets(config.shares);
cache.totalAssets = totalAssets();
// Cannot process more assets than the
if (cache.assets > totalWithdrawableAssets()) {
revert InsufficientAvailableAssets();
}
// If totalIdle can covers the amount fulfill directly
if (cache.totalIdle >= cache.assets) {
cache.sharesFulfilled = config.shares;
cache.totalClaimableWithdraw = cache.assets;
}
// Otherwise perform Superform withdrawals
else {
// Cache amount to withdraw before reducing totalIdle
cache.amountToWithdraw = cache.assets - cache.totalIdle;
// Use totalIdle to fulfill the request
if (cache.totalIdle > 0) {
cache.totalClaimableWithdraw = cache.totalIdle;
cache.sharesFulfilled = _convertToShares(cache.totalIdle, cache.totalAssets);
}
_prepareWithdrawalRoute(cache, false);
// Handle this chain withdrawals
_processThisChainWithdrawals(config, cache);
// Handle external chain withdrawals
if (cache.isSingleChain) {
_processSingleChainWithdrawals(config, cache);
}
if (cache.isMultiChain) {
if (!cache.isMultiVault) {
_processMultiChainSingleVault(config, cache);
} else {
_processMultiChainMultiVault(config, cache);
}
}
}
// Optimistically deduct all assets to withdraw from the total
_totalIdle = cache.totalIdle.toUint128();
_totalIdle -= cache.totalClaimableWithdraw.toUint128();
_totalDebt = cache.totalDebt.toUint128();
// Check that totalAssets was actually reduced by the amount to withdraw
if (totalAssets() > cache.totalAssets - cache.amountToWithdraw) {
revert AssetsNotLiquidated();
}
emit ProcessRedeemRequest(config.controller, config.shares);
pendingProcessedShares[config.controller] += config.shares - cache.sharesFulfilled;
// Burn all shares from this contract(they already have been transferred)
_burn(address(this), config.shares);
// Fulfill request with instant withdrawals only
_fulfillRedeemRequest(cache.sharesFulfilled, cache.totalClaimableWithdraw, config.controller, true);
}
/// @dev Process withdrawals from vaults on the current chain
/// @param config The configuration for the redemption request
/// @param cache The cache structure for storing intermediate calculation results
function _processThisChainWithdrawals(
ProcessRedeemRequestConfig memory config,
ProcessRedeemRequestCache memory cache
)
private
{
uint256 chainIndex = chainIndexes[THIS_CHAIN_ID];
if (cache.lens[chainIndex] == 0) return;
if (cache.lens[chainIndex] == 1) {
// Process single vault
uint256 sharesAmount = cache.sharesPerVault[chainIndex][0];
uint256 assetsAmount = cache.assetsPerVault[chainIndex][0];
uint256 superformId = cache.dstVaults[chainIndex][0];
uint256 withdrawn =
_liquidateSingleDirectSingleVault(vaults[superformId].vaultAddress, sharesAmount, 0, address(this));
cache.sharesFulfilled += _convertToShares(assetsAmount, cache.totalAssets);
_reduceVaultDebt(superformId, assetsAmount);
cache.totalClaimableWithdraw += withdrawn;
cache.totalIdle += withdrawn;
} else {
// Process multi vault
uint256 len = cache.lens[chainIndex];
address[] memory vaultAddresses = new address[](len);
uint256[] memory amounts = new uint256[](len);
uint256 requestedAssets;
for (uint256 i = 0; i < len;) {
vaultAddresses[i] = vaults[cache.dstVaults[chainIndex][i]].vaultAddress;
amounts[i] = cache.sharesPerVault[chainIndex][i];
uint256 superformId = cache.dstVaults[chainIndex][i];
requestedAssets += cache.assetsPerVault[chainIndex][i];
_reduceVaultDebt(superformId, cache.assetsPerVault[chainIndex][i]);
unchecked {
++i;
}
}
uint256 withdrawn = _liquidateSingleDirectMultiVault(
vaultAddresses, amounts, _getEmptyuintArray(amounts.length), address(this)
);
cache.totalClaimableWithdraw += withdrawn;
cache.sharesFulfilled += _convertToShares(requestedAssets, cache.totalAssets);
cache.totalIdle += withdrawn;
}
}
/// @dev Process withdrawals from vaults on a single external chain
/// @param config The configuration for the redemption request
/// @param cache The cache structure for storing intermediate calculation results
function _processSingleChainWithdrawals(
ProcessRedeemRequestConfig memory config,
ProcessRedeemRequestCache memory cache
)
private
{
if (!cache.isMultiVault) {
// Single chain, single vault
uint256 superformId;
uint256 amount;
uint64 chainId;
for (uint256 i = 0; i < N_CHAINS;) {
if (DST_CHAINS[i] == THIS_CHAIN_ID) {
unchecked {
++i;
}
continue;
}
if (cache.lens[i] > 0) {
chainId = DST_CHAINS[i];
uint256 chainIndex = chainIndexes[chainId];
superformId = cache.dstVaults[i][0];
amount = cache.sharesPerVault[i][0];
_liquidateSingleXChainSingleVault(
chainId, superformId, amount, config.controller, config.sXsV, cache.assetsPerVault[i][0]
);
_reduceVaultDebt(superformId, cache.assetsPerVault[chainIndex][0]);
break;
}
unchecked {
++i;
}
}
} else {
// Single chain, multi vault
uint256[] memory superformIds;
uint256[] memory amounts;
uint64 chainId;
for (uint256 i = 0; i < N_CHAINS;) {
if (DST_CHAINS[i] == THIS_CHAIN_ID) {
unchecked {
++i;
}
continue;
}
if (cache.lens[i] > 0) {
chainId = DST_CHAINS[i];
superformIds = _toDynamicUint256Array(cache.dstVaults[i], cache.lens[i]);
amounts = _toDynamicUint256Array(cache.sharesPerVault[i], cache.lens[i]);
uint256 totalDebtReduction;
for (uint256 j = 0; j < superformIds.length;) {
_reduceVaultDebt(superformIds[j], cache.assetsPerVault[i][j]);
totalDebtReduction += cache.assetsPerVault[i][j];
unchecked {
++j;
}
}
_liquidateSingleXChainMultiVault(
chainId,
superformIds,
amounts,
config.controller,
config.sXmV,
totalDebtReduction,
_toDynamicUint256Array(cache.assetsPerVault[i], cache.lens[i])
);
break;
}
unchecked {
++i;
}
}
}
}
/// @dev Process withdrawals from a single vault on multiple external chains
/// @param config The configuration for the redemption request
/// @param cache The cache structure for storing intermediate calculation results
function _processMultiChainSingleVault(
ProcessRedeemRequestConfig memory config,
ProcessRedeemRequestCache memory cache
)
private
{
uint256 chainsLen;
for (uint256 i = 0; i < cache.lens.length;) {
if (cache.lens[i] > 0) chainsLen++;
unchecked {
++i;
}
}
uint8[][] memory ambIds = new uint8[][](chainsLen);
uint64[] memory dstChainIds = new uint64[](chainsLen);
SingleVaultSFData[] memory singleVaultDatas = new SingleVaultSFData[](chainsLen);
uint256 lastChainsIndex;
for (uint256 i = 0; i < N_CHAINS;) {
if (cache.lens[i] > 0) {
dstChainIds[lastChainsIndex] = DST_CHAINS[i];
++lastChainsIndex;
}
unchecked {
++i;
}
}
uint256[] memory totalDebtReductions = new uint256[](chainsLen);
for (uint256 i = 0; i < N_CHAINS;) {
if (cache.lens[i] == 0) {
unchecked {
++i;
}
continue;
}
totalDebtReductions[cache.lastIndex] = cache.assetsPerVault[i][0];
singleVaultDatas[cache.lastIndex] = SingleVaultSFData({
superformId: cache.dstVaults[i][0],
amount: cache.sharesPerVault[i][0],
outputAmount: config.mXsV.outputAmounts[cache.lastIndex],
maxSlippage: config.mXsV.maxSlippages[cache.lastIndex],
liqRequest: config.mXsV.liqRequests[cache.lastIndex],
permit2data: "",
hasDstSwap: config.mXsV.hasDstSwaps[cache.lastIndex],
retain4626: false,
receiverAddress: config.controller,
receiverAddressSP: address(0),
extraFormData: ""
});
ambIds[cache.lastIndex] = config.mXsV.ambIds[cache.lastIndex];
_reduceVaultDebt(cache.dstVaults[i][0], cache.assetsPerVault[i][0]);
cache.lastIndex++;
unchecked {
++i;
}
}
_liquidateMultiDstSingleVault(ambIds, dstChainIds, singleVaultDatas, config.mXsV.value, totalDebtReductions);
}
function _processMultiChainMultiVault(
ProcessRedeemRequestConfig memory config,
ProcessRedeemRequestCache memory cache
)
private
{
uint256 chainsLen;
for (uint256 i = 0; i < cache.lens.length;) {
if (cache.lens[i] > 0) chainsLen++;
unchecked {
++i;
}
}
uint8[][] memory ambIds = new uint8[][](chainsLen);
uint64[] memory dstChainIds = new uint64[](chainsLen);
MultiVaultSFData[] memory multiVaultDatas = new MultiVaultSFData[](chainsLen);
uint256 lastChainsIndex;
for (uint256 i = 0; i < N_CHAINS;) {
if (cache.lens[i] > 0) {
dstChainIds[lastChainsIndex] = DST_CHAINS[i];
++lastChainsIndex;
}
unchecked {
++i;
}
}
uint256[] memory totalDebtReductions = new uint256[](chainsLen);
uint256[][] memory debtReductionsPerVault = new uint256[][](chainsLen);
for (uint256 i = 0; i < N_CHAINS;) {
if (cache.lens[i] == 0) {
unchecked {
++i;
}
continue;
}
bool[] memory emptyBoolArray = _getEmptyBoolArray(cache.lens[i]);
uint256[] memory superformIds = _toDynamicUint256Array(cache.dstVaults[i], cache.lens[i]);
multiVaultDatas[cache.lastIndex] = MultiVaultSFData({
superformIds: superformIds,
amounts: _toDynamicUint256Array(cache.sharesPerVault[i], cache.lens[i]),
outputAmounts: config.mXmV.outputAmounts[cache.lastIndex],
maxSlippages: config.mXmV.maxSlippages[cache.lastIndex],
liqRequests: config.mXmV.liqRequests[cache.lastIndex],
permit2data: "",
hasDstSwaps: config.mXmV.hasDstSwaps[cache.lastIndex],
retain4626s: emptyBoolArray,
receiverAddress: config.controller,
receiverAddressSP: address(0),
extraFormData: ""
});
ambIds[cache.lastIndex] = config.mXmV.ambIds[cache.lastIndex];
debtReductionsPerVault[cache.lastIndex] = _toDynamicUint256Array(cache.sharesPerVault[i], cache.lens[i]);
for (uint256 j = 0; j < superformIds.length;) {
_reduceVaultDebt(superformIds[j], cache.assetsPerVault[i][j]);
totalDebtReductions[cache.lastIndex] += cache.assetsPerVault[i][j];
unchecked {
++j;
}
}
cache.lastIndex++;
unchecked {
++i;
}
}
_liquidateMultiDstMultiVault(
ambIds, dstChainIds, multiVaultDatas, config.mXmV.value, totalDebtReductions, debtReductionsPerVault
);
}
function _reduceVaultDebt(uint256 superformId, uint256 amount) private {
unchecked {
vaults[superformId].totalDebt =
(amount >= vaults[superformId].totalDebt ? 0 : vaults[superformId].totalDebt - amount).toUint128();
}
}
/// @dev Withdraws assets from a single vault on the same chain
/// @param vault Address of the vault to withdraw from
/// @param amount Amount of shares to redeem
/// @param minAmountOut Minimum amount of assets expected to receive
/// @param receiver Address to receive the withdrawn assets
/// @return withdrawn Amount of assets actually withdrawn
function _liquidateSingleDirectSingleVault(
address vault,
uint256 amount,
uint256 minAmountOut,
address receiver
)
private
returns (uint256 withdrawn)
{
uint256 balanceBefore = asset().balanceOf(address(this));
ERC4626(vault).redeem(amount, address(this), receiver);
withdrawn = asset().balanceOf(address(this)) - balanceBefore;
if (withdrawn < minAmountOut) {
revert InsufficientAssets();
}
}
/// @dev Withdraws assets from multiple vaults on the same chain
/// @param vaults_ Array of vault addresses to withdraw from
/// @param amounts Array of share amounts to redeem from each vault
/// @param minAmountsOut Array of minimum amounts of assets expected from each vault
/// @param receiver Address to receive the withdrawn assets
/// @return withdrawn Total amount of assets withdrawn from all vaults
function _liquidateSingleDirectMultiVault(
address[] memory vaults_,
uint256[] memory amounts,
uint256[] memory minAmountsOut,
address receiver
)
private
returns (uint256 withdrawn)
{
for (uint256 i = 0; i < vaults_.length;) {
withdrawn += _liquidateSingleDirectSingleVault(vaults_[i], amounts[i], minAmountsOut[i], receiver);
unchecked {
++i;
}
}
}
/// @dev Initiates a withdrawal from a single vault on a different chain
/// @param chainId ID of the destination chain
/// @param superformId ID of the superform to withdraw from
/// @param amount Amount of shares to withdraw
/// @param receiver Address to receive the withdrawn assets
/// @param config Configuration for the cross-chain withdrawal
/// @param totalDebtReduction total debt reductions per chain
function _liquidateSingleXChainSingleVault(
uint64 chainId,
uint256 superformId,
uint256 amount,
address receiver,
SingleXChainSingleVaultWithdraw memory config,
uint256 totalDebtReduction
)
private
{
gateway.liquidateSingleXChainSingleVault{ value: config.value }(
chainId, superformId, amount, receiver, config, totalDebtReduction
);
}
/// @dev Initiates withdrawals from multiple vaults on a single different chain
/// @param chainId ID of the destination chain
/// @param superformIds Array of superform IDs to withdraw from
/// @param amounts Array of share amounts to withdraw from each superform
/// @param receiver Address to receive the withdrawn assets
/// @param config Configuration for the cross-chain withdrawals
/// @param totalDebtReduction Total debt reduction for this chain's vaults
function _liquidateSingleXChainMultiVault(
uint64 chainId,
uint256[] memory superformIds,
uint256[] memory amounts,
address receiver,
SingleXChainMultiVaultWithdraw memory config,
uint256 totalDebtReduction,
uint256[] memory debtReductionPerVault
)
private
{
gateway.liquidateSingleXChainMultiVault{ value: config.value }(
chainId, superformIds, amounts, receiver, config, totalDebtReduction, debtReductionPerVault
);
}
/// @dev Initiates withdrawals from a single vault on multiple different chains
/// @param ambIds Array of AMB (Asset Management Bridge) IDs for each chain
/// @param dstChainIds Array of destination chain IDs
/// @param singleVaultDatas Array of SingleVaultSFData structures for each withdrawal
/// @param value Amount of native tokens to send with the transaction
/// @param totalDebtReductions Array of total debt reductions per destination chain
function _liquidateMultiDstSingleVault(
uint8[][] memory ambIds,
uint64[] memory dstChainIds,
SingleVaultSFData[] memory singleVaultDatas,
uint256 value,
uint256[] memory totalDebtReductions
)
private
{
gateway.liquidateMultiDstSingleVault{ value: value }(ambIds, dstChainIds, singleVaultDatas, totalDebtReductions);
}
/// @dev Initiates withdrawals from multiple vaults on multiple different chains
/// @param ambIds Array of AMB (Asset Management Bridge) IDs for each chain
/// @param dstChainIds Array of destination chain IDs
/// @param multiVaultDatas Array of MultiVaultSFData structures for each chain's withdrawals
/// @param value Amount of native tokens to send with the transaction
/// @param totalDebtReduction Array of total debt reductions per chain
/// @param debtReductionsPerVault Array of arrays detailing debt reductions per vault per chain
function _liquidateMultiDstMultiVault(
uint8[][] memory ambIds,
uint64[] memory dstChainIds,
MultiVaultSFData[] memory multiVaultDatas,
uint256 value,
uint256[] memory totalDebtReduction,
uint256[][] memory debtReductionsPerVault
)
private
{
gateway.liquidateMultiDstMultiVault{ value: value }(
ambIds, dstChainIds, multiVaultDatas, totalDebtReduction, debtReductionsPerVault
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol";
/// @title ERC7540 Request Type
/// @notice Represents a request in the ERC7540 standard
/// @dev This type is a simple wrapper around a uint256 value
type ERC7540_Request is uint256;
/// @title ERC7540 Filled Request Structure
/// @notice Holds information about a filled request
/// @dev This struct is used to store the assets and shares of a filled ERC7540 request
struct ERC7540_FilledRequest {
uint256 assets; // The number of assets involved in the request
uint256 shares; // The number of shares associated with the request
}
/// @title ERC7540 Library
/// @notice Library for handling ERC7540 requests and conversions
/// @dev This library provides utility functions for converting between assets and shares in ERC7540 requests
library ERC7540Lib {
/// @notice Converts a given amount of assets to shares, rounding up
/// @dev Uses full multiplication and division with rounding up
/// @param self The filled request (ERC7540_FilledRequest) to operate on
/// @param assets The amount of assets to convert to shares
/// @return The equivalent amount of shares, rounded up
function convertToSharesUp(ERC7540_FilledRequest memory self, uint256 assets) internal pure returns (uint256) {
return FixedPointMathLib.fullMulDivUp(self.shares, assets, self.assets);
}
/// @notice Converts a given amount of assets to shares
/// @dev Uses full multiplication and division without rounding
/// @param self The filled request (ERC7540_FilledRequest) to operate on
/// @param assets The amount of assets to convert to shares
/// @return The equivalent amount of shares
function convertToShares(ERC7540_FilledRequest memory self, uint256 assets) internal pure returns (uint256) {
return FixedPointMathLib.fullMulDiv(self.shares, assets, self.assets);
}
/// @notice Converts a given amount of shares to assets
/// @dev Uses full multiplication and division with rounding up
/// @param self The filled request (ERC7540_FilledRequest) to operate on
/// @param shares The amount of shares to convert to assets
/// @return The equivalent amount of assets, rounded up
function convertToAssets(ERC7540_FilledRequest memory self, uint256 shares) internal pure returns (uint256) {
return FixedPointMathLib.fullMulDiv(self.assets, shares, self.shares);
}
/// @notice Converts a given amount of shares to assets, rounding up
/// @dev Uses full multiplication and division with rounding up
/// @param self The filled request (ERC7540_FilledRequest) to operate on
/// @param shares The amount of shares to convert to assets
/// @return The equivalent amount of assets, rounded up
function convertToAssetsUp(ERC7540_FilledRequest memory self, uint256 shares) internal pure returns (uint256) {
return FixedPointMathLib.fullMulDivUp(self.assets, shares, self.shares);
}
/// @notice Adds a value to an ERC7540_Request
/// @dev Adds a uint256 value to the underlying uint256 of the ERC7540_Request
/// @param self The ERC7540_Request to operate on
/// @param x The value to add
/// @return A new ERC7540_Request with the added value
function add(ERC7540_Request self, uint256 x) internal pure returns (ERC7540_Request) {
return ERC7540_Request.wrap(ERC7540_Request.unwrap(self) + x);
}
/// @notice Subtracts a value from an ERC7540_Request
/// @dev Subtracts a uint256 value from the underlying uint256 of the ERC7540_Request
/// @param self The ERC7540_Request to operate on
/// @param x The value to subtract
/// @return A new ERC7540_Request with the subtracted value
function sub(ERC7540_Request self, uint256 x) internal pure returns (ERC7540_Request) {
return ERC7540_Request.wrap(ERC7540_Request.unwrap(self) - x);
}
function sub0(ERC7540_Request self, uint256 x) internal pure returns (ERC7540_Request) {
return ERC7540_Request.wrap(x > ERC7540_Request.unwrap(self) ? 0 : ERC7540_Request.unwrap(self) - x);
}
/// @notice Unwraps an ERC7540_Request to retrieve the underlying uint256 value
/// @dev Retrieves the raw uint256 value wrapped by the ERC7540_Request
/// @param self The ERC7540_Request to unwrap
/// @return The raw uint256 value of the request
function unwrap(ERC7540_Request self) internal pure returns (uint256) {
return ERC7540_Request.unwrap(self);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ModuleBase } from "common/Lib.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { SafeCastLib } from "solady/utils/SafeCastLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import {
LiqRequest,
MultiDstMultiVaultStateReq,
MultiDstSingleVaultStateReq,
MultiVaultSFData,
MultiXChainMultiVaultWithdraw,
MultiXChainSingleVaultWithdraw,
SingleDirectMultiVaultStateReq,
SingleDirectSingleVaultStateReq,
SingleVaultSFData,
SingleXChainMultiVaultStateReq,
SingleXChainMultiVaultWithdraw,
SingleXChainSingleVaultStateReq,
SingleXChainSingleVaultWithdraw,
VaultConfig,
VaultData,
VaultLib
} from "types/Lib.sol";
/// @title EmergencyAssetsManager
/// @notice Emergency module for recovering cross-chain assets when normal bridging routes are unavailable
/// @dev This module provides emergency functions to divest assets from vaults across chains when standard
/// bridging paths are compromised or inaccessible. The assets are recovered manually by authorized admins.
/// The module updates internal accounting but skips the normal receiver validation to allow manual recovery.
contract EmergencyAssetsManager is ModuleBase {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* LIBRARIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Library for vault-related operations
using VaultLib for VaultData;
/// @dev Safe casting operations for uint
using SafeCastLib for uint256;
/// @dev Safe transfer operations for ERC20 tokens
using SafeTransferLib for address;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Thrown when there are not enough assets to fulfill a request
error InsufficientAssets();
/// @notice Thrown when attempting to interact with a vault that is not listed in the portfolio
error VaultNotListed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when assets are emergency divested from vaults
event EmergencyDivest(uint256 amount);
/// @dev Emitted when cross-chain emergency divestment is settled
event SettleXChainDivest(uint256 assets);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMERGENCY DIVEST */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Emergency withdrawal of assets from a single vault on a different chain
/// @dev Initiates a cross-chain withdrawal through the gateway contract without receiver validation.
/// Updates debt tracking for the source vault. Only callable by emergency admins.
/// @param req The withdrawal request containing target chain, vault, and amount details
function emergencyDivestSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req)
external
payable
onlyRoles(EMERGENCY_ADMIN_ROLE)
{
uint256 sharesValue = gateway.divestSingleXChainSingleVault{ value: msg.value }(req, false);
_totalDebt = _sub0(_totalDebt, sharesValue).toUint128();
vaults[req.superformData.superformId].totalDebt =
_sub0(vaults[req.superformData.superformId].totalDebt, sharesValue).toUint128();
emit EmergencyDivest(sharesValue);
}
/// @notice Emergency withdrawal of assets from multiple vaults on a single different chain
/// @dev Processes emergency withdrawals from multiple vaults on the same target chain.
/// Updates debt tracking for all source vaults. Only callable by emergency admins.
/// @param req The withdrawal request containing target chain and multiple vault details
function emergencyDivestSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req)
external
payable
onlyRoles(EMERGENCY_ADMIN_ROLE)
{
uint256 totalAmount = gateway.divestSingleXChainMultiVault{ value: msg.value }(req, false);
for (uint256 i = 0; i < req.superformsData.superformIds.length;) {
uint256 superformId = req.superformsData.superformIds[i];
VaultData memory vault = vaults[superformId];
uint256 divestAmount = vault.convertToAssets(req.superformsData.amounts[i], asset(), true);
vault.totalDebt = _sub0(vaults[superformId].totalDebt, divestAmount).toUint128();
vaults[superformId] = vault;
unchecked {
++i;
}
}
_totalDebt = _sub0(_totalDebt, totalAmount).toUint128();
emit EmergencyDivest(totalAmount);
}
/// @notice Emergency withdrawal of assets from a single vault across multiple chains
/// @dev Initiates emergency withdrawals from the same vault type across different chains.
/// Updates debt tracking for all source vaults. Only callable by emergency admins.
/// @param req The withdrawal request containing multiple chain and single vault details
function emergencyDivestMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req)
external
payable
onlyRoles(EMERGENCY_ADMIN_ROLE)
{
uint256 totalAmount = gateway.divestMultiXChainSingleVault{ value: msg.value }(req, false);
for (uint256 i = 0; i < req.superformsData.length;) {
uint256 superformId = req.superformsData[i].superformId;
VaultData memory vault = vaults[superformId];
uint256 divestAmount = vault.convertToAssets(req.superformsData[i].amount, asset(), true);
vault.totalDebt = _sub0(vaults[superformId].totalDebt, divestAmount).toUint128();
vaults[superformId] = vault;
unchecked {
++i;
}
}
_totalDebt = _sub0(_totalDebt, totalAmount).toUint128();
emit EmergencyDivest(totalAmount);
}
/// @notice Emergency withdrawal of assets from multiple vaults across multiple chains
/// @dev Processes emergency withdrawals from different vaults across multiple chains.
/// Updates debt tracking for all source vaults. Only callable by emergency admins.
/// @param req The withdrawal request containing multiple chain and multiple vault details
function emergencyDivestMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req)
external
payable
onlyRoles(EMERGENCY_ADMIN_ROLE)
{
uint256 totalAmount = gateway.divestMultiXChainMultiVault{ value: msg.value }(req, false);
for (uint256 i = 0; i < req.superformsData.length;) {
uint256[] memory superformIds = req.superformsData[i].superformIds;
for (uint256 j = 0; j < superformIds.length;) {
uint256 superformId = superformIds[j];
VaultData memory vault = vaults[superformId];
uint256 divestAmount = vault.convertToAssets(req.superformsData[i].amounts[j], asset(), true);
vault.totalDebt = _sub0(vaults[superformId].totalDebt, divestAmount).toUint128();
vaults[superformId] = vault;
unchecked {
++j;
}
}
unchecked {
++i;
}
}
_totalDebt = _sub0(_totalDebt, totalAmount).toUint128();
emit EmergencyDivest(totalAmount);
}
/// @notice Returns the function selectors supported by this module
/// @dev Used for module registration and discovery
/// @return Array of 4-byte function selectors for all emergency divest functions
function selectors() public pure returns (bytes4[] memory) {
bytes4[] memory s = new bytes4[](4);
s[0] = this.emergencyDivestMultiXChainMultiVault.selector;
s[1] = this.emergencyDivestMultiXChainSingleVault.selector;
s[2] = this.emergencyDivestSingleXChainMultiVault.selector;
s[3] = this.emergencyDivestSingleXChainSingleVault.selector;
return s;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an overflow.
error RPowOverflow();
/// @dev The mantissa is too big to fit.
error MantissaOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, due to an multiplication overflow.
error SMulWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error SDivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The input outside the acceptable domain.
error OutOfDomain();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if gt(x, div(not(0), y)) {
if y {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if gt(x, div(not(0), y)) {
if y {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
}
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, WAD)
// Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
if iszero(mul(y, eq(sdiv(z, WAD), x))) {
mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
/// Note: This function is an approximation.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is less than 0.5 we return zero.
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
if (x <= -41446531673892822313) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
if iszero(slt(x, 135305999368893231589)) {
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
revert(0x1c, 0x04)
}
}
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// `k` is in the range `[-61, 195]`.
// Evaluate using a (6, 7)-term rational approximation.
// `p` is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already `2**96` too large.
r := sdiv(p, q)
}
// r should be in the range `(0.09, 0.25) * 2**96`.
// We now need to multiply r by:
// - The scale factor `s ≈ 6.031367120`.
// - The `2**k` factor from the range reduction.
// - The `1e18 / 2**96` factor for base conversion.
// We do this all at once, with an intermediate result in `2**213`
// basis, so the final right shift is always by a positive amount.
r = int256(
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
);
}
}
/// @dev Returns `ln(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function lnWad(int256 x) internal pure returns (int256 r) {
/// @solidity memory-safe-assembly
assembly {
// We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
// We do this by multiplying by `2**96 / 10**18`. But since
// `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
// and add `ln(2**96 / 10**18)` at the end.
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// We place the check here for more optimal stack operations.
if iszero(sgt(x, 0)) {
mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
revert(0x1c, 0x04)
}
// forgefmt: disable-next-item
r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
x := shr(159, shl(r, x))
// Evaluate using a (8, 8)-term rational approximation.
// `p` is made monic, we will multiply by a scale factor later.
// forgefmt: disable-next-item
let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
sar(96, mul(add(43456485725739037958740375743393,
sar(96, mul(add(24828157081833163892658089445524,
sar(96, mul(add(3273285459638523848632254066296,
x), x))), x))), x)), 11111509109440967052023855526967)
p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
// `q` is monic by convention.
let q := add(5573035233440673466300451813936, x)
q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
q := add(909429971244387300277376558375, sar(96, mul(x, q)))
// `p / q` is in the range `(0, 0.125) * 2**96`.
// Finalization, we need to:
// - Multiply by the scale factor `s = 5.549…`.
// - Add `ln(2**96 / 10**18)`.
// - Add `k * ln(2)`.
// - Multiply by `10**18 / 2**96 = 5**18 >> 78`.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already `2**96` too large.
p := sdiv(p, q)
// Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
p := mul(1677202110996718588342820967067443963516166, p)
// Add `ln(2) * k * 5**18 * 2**192`.
// forgefmt: disable-next-item
p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
// Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
// Base conversion: mul `2**18 / 2**192`.
r := sar(174, p)
}
}
/// @dev Returns `W_0(x)`, denominated in `WAD`.
/// See: https://en.wikipedia.org/wiki/Lambert_W_function
/// a.k.a. Product log function. This is an approximation of the principal branch.
/// Note: This function is an approximation. Monotonically increasing.
function lambertW0Wad(int256 x) internal pure returns (int256 w) {
// forgefmt: disable-next-item
unchecked {
if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
(int256 wad, int256 p) = (int256(WAD), x);
uint256 c; // Whether we need to avoid catastrophic cancellation.
uint256 i = 4; // Number of iterations.
if (w <= 0x1ffffffffffff) {
if (-0x4000000000000 <= w) {
i = 1; // Inputs near zero only take one step to converge.
} else if (w <= -0x3ffffffffffffff) {
i = 32; // Inputs near `-1/e` take very long to converge.
}
} else if (uint256(w >> 63) == uint256(0)) {
/// @solidity memory-safe-assembly
assembly {
// Inline log2 for more performance, since the range is small.
let v := shr(49, w)
let l := shl(3, lt(0xff, v))
l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
c := gt(l, 60)
i := add(2, add(gt(l, 53), c))
}
} else {
int256 ll = lnWad(w = lnWad(w));
/// @solidity memory-safe-assembly
assembly {
// `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
i := add(3, iszero(shr(68, x)))
c := iszero(shr(143, x))
}
if (c == uint256(0)) {
do { // If `x` is big, use Newton's so that intermediate values won't overflow.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := mul(w, div(e, wad))
w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
}
if (p <= w) break;
p = w;
} while (--i != uint256(0));
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
return w;
}
}
do { // Otherwise, use Halley's for faster convergence.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := add(w, wad)
let s := sub(mul(w, e), mul(x, wad))
w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
}
if (p <= w) break;
p = w;
} while (--i != c);
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
// For certain ranges of `x`, we'll use the quadratic-rate recursive formula of
// R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation.
if (c == uint256(0)) return w;
int256 t = w | 1;
/// @solidity memory-safe-assembly
assembly {
x := sdiv(mul(x, wad), t)
}
x = (t * (wad + lnWad(x)));
/// @solidity memory-safe-assembly
assembly {
w := sdiv(x, add(wad, t))
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GENERAL NUMBER UTILITIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// 512-bit multiply `[p1 p0] = x * y`.
// Compute the product mod `2**256` and mod `2**256 - 1`
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that `product = p1 * 2**256 + p0`.
// Temporarily use `result` as `p0` to save gas.
result := mul(x, y) // Lower 256 bits of `x * y`.
for {} 1 {} {
// If overflows.
if iszero(mul(or(iszero(x), eq(div(result, x), y)), d)) {
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(result, lt(mm, result))) // Upper 256 bits of `x * y`.
/*------------------- 512 by 256 division --------------------*/
// Make division exact by subtracting the remainder from `[p1 p0]`.
let r := mulmod(x, y, d) // Compute remainder using mulmod.
let t := and(d, sub(0, d)) // The least significant bit of `d`. `t >= 1`.
// Make sure the result is less than `2**256`. Also prevents `d == 0`.
// Placing the check here seems to give more optimal stack operations.
if iszero(gt(d, p1)) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
d := div(d, t) // Divide `d` by `t`, which is a power of two.
// Invert `d mod 2**256`
// Now that `d` is an odd number, it has an inverse
// modulo `2**256` such that `d * inv = 1 mod 2**256`.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, `d * inv = 1 mod 2**4`.
let inv := xor(2, mul(3, d))
// Now use 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.
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
result :=
mul(
// Divide [p1 p0] by the factors of two.
// Shift in bits from `p1` into `p0`. For this we need
// to flip `t` such that it is `2**256 / t`.
or(
mul(sub(p1, gt(r, result)), add(div(sub(0, t), t), 1)),
div(sub(result, r), t)
),
mul(sub(2, mul(d, inv)), inv) // inverse mod 2**256
)
break
}
result := div(result, d)
break
}
}
}
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Behavior is undefined if `d` is zero or the final result cannot fit in 256 bits.
/// Performs the full 512 bit calculation regardless.
function fullMulDivUnchecked(uint256 x, uint256 y, uint256 d)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
result := mul(x, y)
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(result, lt(mm, result)))
let t := and(d, sub(0, d))
let r := mulmod(x, y, d)
d := div(d, t)
let inv := xor(2, mul(3, d))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
result :=
mul(
or(mul(sub(p1, gt(r, result)), add(div(sub(0, t), t), 1)), div(sub(result, r), t)),
mul(sub(2, mul(d, inv)), inv)
)
}
}
/// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Uniswap-v3-core under MIT license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
result = fullMulDiv(x, y, d);
/// @solidity memory-safe-assembly
assembly {
if mulmod(x, y, d) {
result := add(result, 1)
if iszero(result) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := div(z, d)
}
}
/// @dev Returns `ceil(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(z, d))), div(z, d))
}
}
/// @dev Returns `ceil(x / d)`.
/// Reverts if `d` is zero.
function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
if iszero(d) {
mstore(0x00, 0x65244e4e) // `DivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(x, d))), div(x, d))
}
}
/// @dev Returns `max(0, x - y)`.
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, uint256 x, uint256 y) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`.
/// Reverts if the computation overflows.
function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`.
if x {
z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x`
let half := shr(1, b) // Divide `b` by 2.
// Divide `y` by 2 every iteration.
for { y := shr(1, y) } y { y := shr(1, y) } {
let xx := mul(x, x) // Store x squared.
let xxRound := add(xx, half) // Round to the nearest number.
// Revert if `xx + half` overflowed, or if `x ** 2` overflows.
if or(lt(xxRound, xx), shr(128, x)) {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
x := div(xxRound, b) // Set `x` to scaled `xxRound`.
// If `y` is odd:
if and(y, 1) {
let zx := mul(z, x) // Compute `z * x`.
let zxRound := add(zx, half) // Round to the nearest number.
// If `z * x` overflowed or `zx + half` overflowed:
if or(xor(div(zx, x), z), lt(zxRound, zx)) {
// Revert if `x` is non-zero.
if x {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
}
z := div(zxRound, b) // Return properly scaled `zxRound`.
}
}
}
}
}
/// @dev Returns the square root of `x`, rounded down.
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// Let `y = x / 2**r`. We check `y >= 2**(k + 8)`
// but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffffff, shr(r, x))))
z := shl(shr(1, r), z)
// Goal was to get `z*z*y` within a small factor of `x`. More iterations could
// get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
// We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
// That's not possible if `x < 256` but we can just verify those cases exhaustively.
// Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
// Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
// Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.
// For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
// is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
// with largest error when `s = 1` and when `s = 256` or `1/256`.
// Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
// Then we can estimate `sqrt(y)` using
// `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.
// There is no overflow risk here since `y < 2**136` after the first branch above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If `x+1` is a perfect square, the Babylonian method cycles between
// `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
z := sub(z, lt(div(x, z), z))
}
}
/// @dev Returns the cube root of `x`, rounded down.
/// Credit to bout3fiddy and pcaversaccio under AGPLv3 license:
/// https://github.com/pcaversaccio/snekmate/blob/main/src/utils/Math.vy
/// Formally verified by xuwinnie:
/// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
function cbrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// Makeshift lookup table to nudge the approximate log2 result.
z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3)))
// Newton-Raphson's.
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
// Round down.
z := sub(z, lt(div(x, mul(z, z)), z))
}
}
/// @dev Returns the square root of `x`, denominated in `WAD`, rounded down.
function sqrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 18) return sqrt(x * 10 ** 18);
z = (1 + sqrt(x)) * 10 ** 9;
z = (fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1;
}
/// @solidity memory-safe-assembly
assembly {
z := sub(z, gt(999999999999999999, sub(mulmod(z, z, x), 1))) // Round down.
}
}
/// @dev Returns the cube root of `x`, denominated in `WAD`, rounded down.
/// Formally verified by xuwinnie:
/// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
function cbrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 36) return cbrt(x * 10 ** 36);
z = (1 + cbrt(x)) * 10 ** 12;
z = (fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3;
}
/// @solidity memory-safe-assembly
assembly {
let p := x
for {} 1 {} {
if iszero(shr(229, p)) {
if iszero(shr(199, p)) {
p := mul(p, 100000000000000000) // 10 ** 17.
break
}
p := mul(p, 100000000) // 10 ** 8.
break
}
if iszero(shr(249, p)) { p := mul(p, 100) }
break
}
let t := mulmod(mul(z, z), z, p)
z := sub(z, gt(lt(t, shr(1, p)), iszero(t))) // Round down.
}
}
/// @dev Returns the factorial of `x`.
function factorial(uint256 x) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(x, 58)) {
mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`.
revert(0x1c, 0x04)
}
for {} x { x := sub(x, 1) } { result := mul(result, x) }
}
}
/// @dev Returns the log2 of `x`.
/// Equivalent to computing the index of the most significant bit (MSB) of `x`.
/// Returns 0 if `x` is zero.
function log2(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Returns the log2 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log2Up(uint256 x) internal pure returns (uint256 r) {
r = log2(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(r, 1), x))
}
}
/// @dev Returns the log10 of `x`.
/// Returns 0 if `x` is zero.
function log10(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 100000000000000000000000000000000000000)) {
x := div(x, 100000000000000000000000000000000000000)
r := 38
}
if iszero(lt(x, 100000000000000000000)) {
x := div(x, 100000000000000000000)
r := add(r, 20)
}
if iszero(lt(x, 10000000000)) {
x := div(x, 10000000000)
r := add(r, 10)
}
if iszero(lt(x, 100000)) {
x := div(x, 100000)
r := add(r, 5)
}
r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999)))))
}
}
/// @dev Returns the log10 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log10Up(uint256 x) internal pure returns (uint256 r) {
r = log10(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(exp(10, r), x))
}
}
/// @dev Returns the log256 of `x`.
/// Returns 0 if `x` is zero.
function log256(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(shr(3, r), lt(0xff, shr(r, x)))
}
}
/// @dev Returns the log256 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log256Up(uint256 x) internal pure returns (uint256 r) {
r = log256(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(shl(3, r), 1), x))
}
}
/// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`.
/// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent).
function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) {
/// @solidity memory-safe-assembly
assembly {
mantissa := x
if mantissa {
if iszero(mod(mantissa, 1000000000000000000000000000000000)) {
mantissa := div(mantissa, 1000000000000000000000000000000000)
exponent := 33
}
if iszero(mod(mantissa, 10000000000000000000)) {
mantissa := div(mantissa, 10000000000000000000)
exponent := add(exponent, 19)
}
if iszero(mod(mantissa, 1000000000000)) {
mantissa := div(mantissa, 1000000000000)
exponent := add(exponent, 12)
}
if iszero(mod(mantissa, 1000000)) {
mantissa := div(mantissa, 1000000)
exponent := add(exponent, 6)
}
if iszero(mod(mantissa, 10000)) {
mantissa := div(mantissa, 10000)
exponent := add(exponent, 4)
}
if iszero(mod(mantissa, 100)) {
mantissa := div(mantissa, 100)
exponent := add(exponent, 2)
}
if iszero(mod(mantissa, 10)) {
mantissa := div(mantissa, 10)
exponent := add(exponent, 1)
}
}
}
}
/// @dev Convenience function for packing `x` into a smaller number using `sci`.
/// The `mantissa` will be in bits [7..255] (the upper 249 bits).
/// The `exponent` will be in bits [0..6] (the lower 7 bits).
/// Use `SafeCastLib` to safely ensure that the `packed` number is small
/// enough to fit in the desired unsigned integer type:
/// ```
/// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether));
/// ```
function packSci(uint256 x) internal pure returns (uint256 packed) {
(x, packed) = sci(x); // Reuse for `mantissa` and `exponent`.
/// @solidity memory-safe-assembly
assembly {
if shr(249, x) {
mstore(0x00, 0xce30380c) // `MantissaOverflow()`.
revert(0x1c, 0x04)
}
packed := or(shl(7, x), packed)
}
}
/// @dev Convenience function for unpacking a packed number from `packSci`.
function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) {
unchecked {
unpacked = (packed >> 7) * 10 ** (packed & 0x7f);
}
}
/// @dev Returns the average of `x` and `y`. Rounds towards zero.
function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = (x & y) + ((x ^ y) >> 1);
}
}
/// @dev Returns the average of `x` and `y`. Rounds towards negative infinity.
function avg(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = (x >> 1) + (y >> 1) + (x & y & 1);
}
}
/// @dev Returns the absolute value of `x`.
function abs(int256 x) internal pure returns (uint256 z) {
unchecked {
z = (uint256(x) + uint256(x >> 255)) ^ uint256(x >> 255);
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(xor(sub(0, gt(x, y)), sub(y, x)), gt(x, y))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(int256 x, int256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(xor(sub(0, sgt(x, y)), sub(y, x)), sgt(x, y))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), slt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), sgt(y, x)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(uint256 x, uint256 minValue, uint256 maxValue)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), gt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), lt(maxValue, z)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, minValue), sgt(minValue, x)))
z := xor(z, mul(xor(z, maxValue), slt(maxValue, z)))
}
}
/// @dev Returns greatest common divisor of `x` and `y`.
function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
for { z := x } y {} {
let t := y
y := mod(z, y)
z := t
}
}
}
/// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`,
/// with `t` clamped between `begin` and `end` (inclusive).
/// Agnostic to the order of (`a`, `b`) and (`end`, `begin`).
/// If `begins == end`, returns `t <= begin ? a : b`.
function lerp(uint256 a, uint256 b, uint256 t, uint256 begin, uint256 end)
internal
pure
returns (uint256)
{
if (begin > end) (t, begin, end) = (~t, ~begin, ~end);
if (t <= begin) return a;
if (t >= end) return b;
unchecked {
if (b >= a) return a + fullMulDiv(b - a, t - begin, end - begin);
return a - fullMulDiv(a - b, t - begin, end - begin);
}
}
/// @dev Returns `a + (b - a) * (t - begin) / (end - begin)`.
/// with `t` clamped between `begin` and `end` (inclusive).
/// Agnostic to the order of (`a`, `b`) and (`end`, `begin`).
/// If `begins == end`, returns `t <= begin ? a : b`.
function lerp(int256 a, int256 b, int256 t, int256 begin, int256 end)
internal
pure
returns (int256)
{
if (begin > end) (t, begin, end) = (~t, ~begin, ~end);
if (t <= begin) return a;
if (t >= end) return b;
// forgefmt: disable-next-item
unchecked {
if (b >= a) return int256(uint256(a) + fullMulDiv(uint256(b - a),
uint256(t - begin), uint256(end - begin)));
return int256(uint256(a) - fullMulDiv(uint256(a - b),
uint256(t - begin), uint256(end - begin)));
}
}
/// @dev Returns if `x` is an even number. Some people may need this.
function isEven(uint256 x) internal pure returns (bool) {
return x & uint256(1) == uint256(0);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RAW NUMBER OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x + y`, without checking for overflow.
function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x + y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x - y`, without checking for underflow.
function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x - y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x * y`, without checking for overflow.
function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = x * y;
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(x, y)
}
}
/// @dev Returns `x / y`, returning 0 if `y` is zero.
function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mod(x, y)
}
}
/// @dev Returns `x % y`, returning 0 if `y` is zero.
function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := smod(x, y)
}
}
/// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := addmod(x, y, d)
}
}
/// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mulmod(x, y, d)
}
}
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "types/Lib.sol";
interface IBaseRouter {
function singleDirectSingleVaultDeposit(SingleDirectSingleVaultStateReq memory req_) external payable;
function singleXChainSingleVaultDeposit(SingleXChainSingleVaultStateReq memory req_) external payable;
function singleDirectMultiVaultDeposit(SingleDirectMultiVaultStateReq memory req_) external payable;
function singleXChainMultiVaultDeposit(SingleXChainMultiVaultStateReq memory req_) external payable;
function multiDstSingleVaultDeposit(MultiDstSingleVaultStateReq calldata req_) external payable;
function multiDstMultiVaultDeposit(MultiDstMultiVaultStateReq calldata req_) external payable;
function singleDirectSingleVaultWithdraw(SingleDirectSingleVaultStateReq memory req_) external payable;
function singleXChainSingleVaultWithdraw(SingleXChainSingleVaultStateReq memory req_) external payable;
function singleDirectMultiVaultWithdraw(SingleDirectMultiVaultStateReq memory req_) external payable;
function singleXChainMultiVaultWithdraw(SingleXChainMultiVaultStateReq memory req_) external payable;
function multiDstSingleVaultWithdraw(MultiDstSingleVaultStateReq calldata req_) external payable;
function multiDstMultiVaultWithdraw(MultiDstMultiVaultStateReq calldata req_) external payable;
function forwardDustToPaymaster(address token_) external;
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IBridgeValidator {
function decodeAmountIn(
bytes calldata txData_,
bool genericSwapDisallowed_
)
external
view
returns (uint256 amount_);
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IERC1155A {
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
)
external
view
returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
)
external;
function allowance(address owner, address operator, uint256 id) external returns (uint256);
}
/// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.19;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
/// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.19;
import { IERC20 } from "./IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
/// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.19;
import { IERC20 } from "./IERC20.sol";
import { IERC20Metadata } from "./IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IHurdleRateOracle {
function getRate(address asset) external view returns (uint256);
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { ISharePriceOracle } from "./ISharePriceOracle.sol";
import { ERC7540Engine } from "modules/Lib.sol";
import {
LiqRequest,
MultiDstMultiVaultStateReq,
MultiDstSingleVaultStateReq,
MultiVaultSFData,
MultiXChainMultiVaultWithdraw,
MultiXChainSingleVaultWithdraw,
ProcessRedeemRequestParams,
SingleDirectMultiVaultStateReq,
SingleDirectSingleVaultStateReq,
SingleVaultSFData,
SingleXChainMultiVaultStateReq,
SingleXChainMultiVaultWithdraw,
SingleXChainSingleVaultStateReq,
SingleXChainSingleVaultWithdraw,
VaultConfig,
VaultData,
VaultLib
} from "types/Lib.sol";
interface IMetaVault {
function WITHDRAWAL_QUEUE_SIZE() external view returns (uint256);
function SECS_PER_YEAR() external view returns (uint256);
function MAX_BPS() external view returns (uint256);
function ADMIN_ROLE() external view returns (uint256);
function MANAGER_ROLE() external view returns (uint256);
function RELAYER_ROLE() external view returns (uint256);
function ORACLE_ROLE() external view returns (uint256);
function EMERGENCY_ADMIN_ROLE() external view returns (uint256);
function N_CHAINS() external view returns (uint256);
function THIS_CHAIN_ID() external view returns (uint64);
function DST_CHAINS(uint256) external view returns (uint64);
function treasury() external view returns (address);
function signerRelayer() external view returns (address);
function gateway() external view returns (address);
function emergencyShutdown() external view returns (bool);
function localWithdrawalQueue(uint256) external view returns (uint256);
function xChainWithdrawalQueue(uint256) external view returns (uint256);
function performanceFeeExempt(address) external view returns (uint256);
function managementFeeExempt(address) external view returns (uint256);
function oracleFeeExempt(address) external view returns (uint256);
function grantRoles(address, uint256) external;
function balanceOf(address) external view returns (uint256);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function asset() external view returns (address);
function totalAssets() external view returns (uint256 assets);
function totalSupply() external view returns (uint256 assets);
function convertToAssets(uint256) external view returns (uint256);
function convertToShares(uint256) external view returns (uint256);
function convertToSuperPositions(uint256 superformId, uint256 assets) external view returns (uint256);
function totalWithdrawableAssets() external view returns (uint256 assets);
function totalLocalAssets() external view returns (uint256 assets);
function totalXChainAssets() external view returns (uint256 assets);
function sharePrice() external view returns (uint256);
function totalIdle() external view returns (uint256 assets);
function totalDebt() external view returns (uint256 assets);
function totalDeposits() external view returns (uint256 assets);
function managementFee() external view returns (uint256);
function sharesLockTime() external view returns (uint256);
function performanceFee() external view returns (uint256);
function oracleFee() external view returns (uint256);
function hurdleRate() external view returns (uint256);
function lastReport() external view returns (uint256);
function addVault(
uint32 chainId,
uint256 superformId,
address vault,
uint8 vaultDecimals,
ISharePriceOracle oracle
)
external;
function removeVault(uint256 superformId) external;
function rearrangeWithdrawalQueue(uint8 queueType, uint256[30] calldata newOrder) external;
function vaults(uint256) external view returns (uint32, uint256, ISharePriceOracle, uint8, uint128, address);
function isVaultListed(address vaultAddress) external view returns (bool);
function isVaultListed(uint256 superformId) external view returns (bool);
function getVault(uint256 superformId) external view returns (VaultData memory vault);
function setOperator(address, bool) external;
function isOperator(address, address) external view returns (bool);
function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId);
function deposit(uint256 assets, address to) external returns (uint256 shares);
function deposit(uint256 assets, address to, address controller) external returns (uint256 shares);
function mint(uint256 shares, address to) external returns (uint256 assets);
function mint(uint256 shares, address to, address controller) external returns (uint256 assets);
function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId);
function redeem(uint256 shares, address receiver, address controller) external returns (uint256 assets);
function withdraw(uint256 assets, address receiver, address controller) external returns (uint256 shares);
function positions(address) external view returns (uint256);
function pendingRedeemRequest(address) external view returns (uint256);
function claimableRedeemRequest(address) external view returns (uint256);
function pendingProcessedShares(address) external view returns (uint256);
function pendingDepositRequest(address) external view returns (uint256);
function claimableDepositRequest(address) external view returns (uint256);
function processRedeemRequest(ProcessRedeemRequestParams calldata params) external payable;
function processSignedRequest(
ProcessRedeemRequestParams calldata params,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external;
function nonces(address) external view returns (uint256);
function investSingleDirectSingleVault(
address vaultAddress,
uint256 assets,
uint256 minSharesOut
)
external
returns (uint256 shares);
function investSingleDirectMultiVault(
address[] calldata vaultAddresses,
uint256[] calldata assets,
uint256[] calldata minSharesOuts
)
external
returns (uint256[] memory shares);
function investSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req) external payable;
function investSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req) external payable;
function investMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req) external payable;
function investMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req) external payable;
function divestSingleDirectSingleVault(
address vaultAddress,
uint256 shares,
uint256 minAssetsOut
)
external
returns (uint256 assets);
function divestSingleDirectMultiVault(
address[] calldata vaultAddresses,
uint256[] calldata shares,
uint256[] calldata minAssetsOuts
)
external
returns (uint256[] memory assets);
function setFeeExcemption(
address controller,
uint256 managementFeeExcemption,
uint256 performanceFeeExcemption,
uint256 oracleFeeExcemption
)
external;
function sharePriceWaterMark() external view returns (uint256);
function lastRedeem(address) external view returns (uint256);
function divestSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req) external payable;
function divestSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req) external payable;
function divestMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req) external payable;
function divestMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req) external payable;
function emergencyDivestSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req) external payable;
function emergencyDivestSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req) external payable;
function emergencyDivestMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req) external payable;
function emergencyDivestMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req) external payable;
function setEmergencyShutdown(bool _emergencyShutdown) external;
function setGateway(address) external;
function donate(uint256 assets) external;
function setSharesLockTime(uint24 time) external;
function setManagementFee(uint16 _managementFee) external;
function setPerformanceFee(uint16 _performanceFee) external;
function setOracleFee(uint16 _oracleFee) external;
function setRecoveryAddress(address _recoveryAddress) external;
function fulfillSettledRequest(address controller, uint256 requestedAssets, uint256 settledAssets) external;
function settleXChainInvest(uint256 superformId, uint256 bridgedAssets) external;
function settleXChainDivest(uint256 withdrawn) external;
function multicall(bytes[] calldata data) external returns (bytes[] memory);
function addFunction(bytes4, address, bool) external;
function addFunctions(bytes4[] memory, address, bool) external;
function removeFunction(bytes4) external;
function removeFunctions(bytes4[] memory) external;
function lastFeesCharged() external view returns (uint256);
function chargeGlobalFees() external returns (uint256);
function previewWithdrawalRoute(
address controller,
uint256 shares,
bool despiseDust
)
external
view
returns (ERC7540Engine.ProcessRedeemRequestCache memory cachedRoute);
function setDustThreshold(uint256 dustThreshold) external;
function dustThreshold() external view returns (uint256);
function computeHash(
ProcessRedeemRequestParams calldata params,
uint256 deadline,
uint256 nonce
)
external
pure
returns (bytes32);
function verifySignature(
ProcessRedeemRequestParams calldata params,
uint256 deadline,
uint256 nonce,
uint8 v,
bytes32 r,
bytes32 s
)
external
view
returns (bool);
function onERC1155Received(
address operator,
address from,
uint256 superformId,
uint256 value,
bytes memory data
)
external
returns (bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] memory superformIds,
uint256[] memory values,
bytes memory data
)
external
returns (bytes4);
/// @notice Detailed data structure for a vault
/// @dev Contains key vault parameters and current state
struct VaultDetailedData {
/// @dev The ID of the chain where the vault is deployed
uint32 chainId;
/// @dev The superform ID of the vault in the Superform protocol
uint256 superformId;
/// @dev The oracle that provides the share price for the vault
ISharePriceOracle oracle;
/// @dev The number of decimals used in the ERC4626 shares
uint8 decimals;
/// @dev The total assets invested in the vault
uint128 totalDebt;
/// @dev The address of the vault
address vaultAddress;
/// @dev Owned vault shares
uint256 sharesBalance;
/// @dev Share price of the vault
uint256 sharePrice;
/// @dev Total assets managed by vault
uint256 totalAssets;
}
function getVaultDetailedData(uint256 superformId) external view returns (VaultDetailedData memory);
function getAllVaultsDetailedData() external view returns (VaultDetailedData[] memory);
/// @notice Data structure for vault return metrics
/// @dev Contains total, hurdle and excess returns
struct VaultReturnsData {
/// @dev Total return generated by the vault
uint256 totalReturn;
/// @dev Minimum return required before performance fees
uint256 hurdleReturn;
/// @dev Returns above the hurdle rate
uint256 excessReturn;
}
function getLastEpochVaultReturns() external view returns (VaultReturnsData memory);
function totalReturnsPerShare() external view returns (int256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
/**
* @title VaultReport
* @notice Structure containing vault share price information and metadata
* @param sharePrice The current share price of the vault
* @param lastUpdate Timestamp of the last update
* @param chainId ID of the chain where the vault exists
* @param rewardsDelegate Address to delegate rewards to
* @param vaultAddress Address of the vault
*/
struct VaultReport {
uint256 sharePrice;
uint64 lastUpdate;
uint32 chainId;
address rewardsDelegate;
address vaultAddress;
address asset;
uint256 assetDecimals;
}
/**
* @title ChainlinkResponse
* @notice Structure containing Chainlink price feed response data
* @param price The price of the asset
* @param decimals The number of decimals in the price
* @param timestamp The timestamp of the price data
* @param roundId The round ID of the price data
* @param answeredInRound The round ID of the round in which the price data was reported
*/
struct ChainlinkResponse {
uint256 price;
uint8 decimals;
uint256 timestamp;
uint80 roundId;
uint80 answeredInRound;
}
/**
* @title ISharePriceOracle
* @notice Interface for cross-chain ERC4626 vault share price oracle
*/
interface ISharePriceOracle {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event SharePriceUpdated(uint32 indexed srcChainId, address indexed vault, uint256 price, address rewardsDelegate);
event LzEndpointUpdated(address oldEndpoint, address newEndpoint);
event RoleGranted(address account, uint256 role);
event RoleRevoked(address account, uint256 role);
event PriceFeedSet(uint32 indexed chainId, address indexed asset, address priceFeed);
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Get the chain ID of this oracle
function chainId() external view returns (uint32);
/// @notice Get the admin role identifier
function ADMIN_ROLE() external view returns (uint256);
/// @notice Get the endpoint role identifier
function ENDPOINT_ROLE() external view returns (uint256);
/// @notice Check if an account has a specific role
function hasRole(address account, uint256 role) external view returns (bool);
/// @notice Get all roles assigned to an account
function getRoles(address account) external view returns (uint256);
/// @notice Get current share prices for multiple vaults
function getSharePrices(
address[] calldata vaultAddresses,
address rewardsDelegate
)
external
view
returns (VaultReport[] memory);
/// @notice Get latest share price for a specific vault
function getLatestSharePriceReport(
uint32 _srcChainId,
address _vaultAddress
)
external
view
returns (VaultReport memory);
/// @notice Get latest share price for a specific vault / asset pair
function getLatestSharePrice(
uint32 _srcChainId,
address _vaultAddress,
address _dstAsset
)
external
view
returns (uint256 price, uint64 timestamp);
/// @notice Generate a unique key for a vault's price data
function getPriceKey(uint32 _srcChainId, address _vault) external pure returns (bytes32);
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Update share prices from another chain
function updateSharePrices(uint32 _srcChainId, VaultReport[] calldata _reports) external;
/// @notice Grant a role to an account
function grantRole(address account, uint256 role) external;
/// @notice Revoke a role from an account
function revokeRole(address account, uint256 role) external;
/// @notice Set the LayerZero endpoint address
function setLzEndpoint(address _endpoint) external;
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { IERC1155A } from "./IERC1155A.sol";
interface ISuperPositions is IERC1155A {
function mintSingle(address receiverAddress_, uint256 id_, uint256 amount_) external;
function mintBatch(address receiverAddress_, uint256[] memory ids_, uint256[] memory amounts_) external;
function burnSingle(address srcSender_, uint256 id_, uint256 amount_) external;
function burnBatch(address srcSender_, uint256[] memory ids_, uint256[] memory amounts_) external;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;
/// @title ISuperPositionsReceiver
/// @notice Interface for the cross-chain recovery contract for failed SuperPosition investments
/// @dev This interface defines all public and external functions of the SuperPositionsReceiver contract
interface ISuperPositionsReceiver {
/// @notice Event emitted when tokens are successfully bridged
/// @param token The address of the token being bridged
/// @param amount The amount of tokens bridged
event BridgeInitiated(address indexed token, uint256 amount);
/// @notice Event emitted when a token is approved for spending
/// @param token The address of the approved token
/// @param spender The address of the spender
/// @param amount The approved token amount
event TokenApproval(address indexed token, address indexed spender, uint256 amount);
/// @notice Event emitted when a target contract is whitelisted or removed from whitelist
/// @param target The address of the target contract whose whitelist status changed
/// @param status The new whitelist status (true = whitelisted, false = not whitelisted)
event TargetWhitelisted(address indexed target, bool status);
/// @notice Error thrown when no tokens were transferred during a bridge operation
error NoTokensTransferred();
/// @notice Error thrown when the provided gas limit exceeds the maximum allowed for bridging.
error GasLimitExceeded();
/// @notice Error thrown when a bridge transaction fails
error BridgeTransactionFailed();
/// @notice Error thrown when attempting to recover funds on the source chain
error SourceChainRecoveryNotAllowed();
/// @notice Error thrown when attempting to use a non-whitelisted target in bridgeToken
error TargetNotWhitelisted();
/// @notice Gets the current source chain ID
/// @return The chain ID of the source chain where the gateway is deployed
function sourceChain() external view returns (uint64);
/// @notice Gets the gateway contract address
/// @return The address of the SuperformGateway contract
function gateway() external view returns (address);
/// @notice Gets the SuperPositions contract address
/// @return The address of the SuperPositions (ERC1155) contract
function superPositions() external view returns (address);
/// @notice Gets the maximum gas limit for bridge calls
/// @return The maximum gas limit for bridge transactions
function maxBridgeGasLimit() external view returns (uint256);
/// @notice Gets the constant for admin role
/// @return The role identifier for admin privileges
function ADMIN_ROLE() external view returns (uint256);
/// @notice Gets the constant for recovery role
/// @return The role identifier for recovery admin privileges
function RECOVERY_ROLE() external view returns (uint256);
/// @notice Checks if a target address is whitelisted
/// @param _target The address to check
/// @return status True if the address is whitelisted, false otherwise
function whitelistedTargets(address _target) external view returns (bool status);
/// @notice Updates the gateway contract address
/// @dev Only callable by addresses with ADMIN_ROLE
/// @param _gateway New gateway contract address
function setGateway(address _gateway) external;
/// @notice Updates the maximum gas limit for bridge calls
/// @dev Only callable by addresses with ADMIN_ROLE
/// @param _maxGasLimit New maximum gas limit
function setMaxBridgeGasLimit(uint256 _maxGasLimit) external;
/// @notice Adds or removes a target contract from the whitelist
/// @dev Only callable by admin
/// @param _target The address of the target contract
/// @param _status True to whitelist, false to remove from whitelist
function setTargetWhitelisted(address _target, bool _status) external;
/// @notice Recovers stuck tokens from failed cross-chain operations
/// @dev Can only be called by addresses with RECOVERY_ROLE and only on destination chains
/// @param token The address of the token to recover
/// @param amount The amount of tokens to recover
function recoverFunds(address token, uint256 amount, address to) external;
/// @notice Checks if the contract supports a given interface
/// @dev Used for ERC1155 interface detection
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @return isSupported True if the contract supports the interface, false otherwise
function supportsInterface(bytes4 interfaceId) external pure returns (bool isSupported);
/// @notice Handles the receipt of a single ERC1155 token type
/// @dev This function is called at the end of a `safeTransferFrom` after the balance has been updated
/// @param operator The address which initiated the transfer (i.e. msg.sender)
/// @param from The address which previously owned the token
/// @param superformId The ID of the token being transferred
/// @param value The amount of tokens being transferred
/// @param data Additional data with no specified format
/// @return bytes4 `bytes4(keccak256("onERC1155Received(address,address,uint,uint,bytes)"))`
function onERC1155Received(
address operator,
address from,
uint256 superformId,
uint256 value,
bytes memory data
)
external
returns (bytes4);
/// @notice Handles the receipt of multiple ERC1155 token types
/// @dev This function is called at the end of a `safeBatchTransferFrom` after the balances have been updated
/// @param operator The address which initiated the batch transfer (i.e. msg.sender)
/// @param from The address which previously owned the tokens
/// @param superformIds An array containing ids of each token being transferred (order and length must match values
/// array)
/// @param values An array containing amounts of each token being transferred (order and length must match ids
/// array)
/// @param data Additional data with no specified format
/// @return bytes4 `bytes4(keccak256("onERC1155BatchReceived(address,address,uint[],uint[],bytes)"))`
function onERC1155BatchReceived(
address operator,
address from,
uint256[] memory superformIds,
uint256[] memory values,
bytes memory data
)
external
returns (bytes4);
/// @notice Bridges ERC20 tokens using provided API data
/// @dev Only callable by addresses with ADMIN_ROLE
/// @param _to Target address for the bridge transaction (txTarget from API)
/// @param _txData Transaction data for the bridge (txData from API)
/// @param _token Address of the token to bridge
/// @param _allowanceTarget Approval target for the token (approvalData.allowanceTarget from API)
/// @param _amount Amount of tokens to bridge (approvalData.minimumApprovalAmount from API)
/// @param _gasLimit The gas limit for the bridging and swapping
function bridgeToken(
address payable _to,
bytes memory _txData,
address _token,
address _allowanceTarget,
uint256 _amount,
uint256 _gasLimit
)
external;
// For OwnableRoles functions we should include them or inherit the interface
// Assuming we need to interact with role management
/// @notice Checks if an address has all specified roles
/// @param user The address to check
/// @param roles The roles to check for
/// @return True if the address has all specified roles, false otherwise
function hasAllRoles(address user, uint256 roles) external view returns (bool);
/// @notice Grants roles to an address
/// @param user The address to grant roles to
/// @param roles The roles to grant
function grantRoles(address user, uint256 roles) external;
/// @notice Revokes roles from an address
/// @param user The address to revoke roles from
/// @param roles The roles to revoke
function revokeRoles(address user, uint256 roles) external;
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface ISuperRegistry {
function getBridgeValidator(uint8 bridgeId_) external view returns (address bridgeValidator_);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;
interface ISuperformFactory {
enum PauseStatus {
NON_PAUSED,
PAUSED
}
event FormImplementationAdded(
address indexed formImplementation, uint256 indexed formImplementationId, uint8 indexed formStateRegistryId
);
event SuperformCreated(
uint256 indexed formImplementationId, address indexed vault, uint256 indexed superformId, address superform
);
event SuperRegistrySet(address indexed superRegistry);
event FormImplementationPaused(uint256 indexed formImplementationId, PauseStatus indexed paused);
function getFormCount() external view returns (uint256 forms_);
function getSuperformCount() external view returns (uint256 superforms_);
function getFormImplementation(uint32 formImplementationId_) external view returns (address formImplementation_);
function getFormStateRegistryId(uint32 formImplementationId_) external view returns (uint8 stateRegistryId_);
function isFormImplementationPaused(uint32 formImplementationId_) external view returns (bool paused_);
function getSuperform(uint256 superformId_)
external
pure
returns (address superform_, uint32 formImplementationId_, uint64 chainId_);
function isSuperform(uint256 superformId_) external view returns (bool isSuperform_);
function getAllSuperformsFromVault(address vault_)
external
view
returns (uint256[] memory superformIds_, address[] memory superforms_);
function addFormImplementation(
address formImplementation_,
uint32 formImplementationId_,
uint8 formStateRegistryId_
)
external;
function createSuperform(
uint32 formImplementationId_,
address vault_
)
external
returns (uint256 superformId_, address superform_);
function stateSyncBroadcast(bytes memory data_) external payable;
function changeFormImplementationPauseStatus(
uint32 formImplementationId_,
PauseStatus status_,
bytes memory extraData_
)
external
payable;
}
/// SPDX-License-Identifier: MIT
import { ISuperPositions } from "../interfaces/ISuperPositions.sol";
import {
MultiDstMultiVaultStateReq,
MultiDstSingleVaultStateReq,
MultiVaultSFData,
SingleDirectMultiVaultStateReq,
SingleDirectSingleVaultStateReq,
SingleVaultSFData,
SingleXChainMultiVaultStateReq,
SingleXChainSingleVaultStateReq
} from "../types/SuperformTypes.sol";
import { SingleXChainMultiVaultWithdraw, SingleXChainSingleVaultWithdraw } from "../types/VaultTypes.sol";
interface ISuperformGateway {
function getRequestsQueue() external view returns (bytes32[] memory requestIds);
function recoveryAddress() external view returns (address);
function grantRoles(address, uint256) external;
function ADMIN_ROLE() external view returns (uint256);
function RELAYER_ROLE() external view returns (uint256);
function setRecoveryAddress(address _newRecoveryAddress) external;
function superPositions() external view returns (ISuperPositions);
function notifyRefund(uint256 superformId, uint256 amount) external;
function notifyBatchRefund(uint256[] calldata superformIds, uint256[] calldata values) external;
function totalpendingXChainInvests() external view returns (uint256);
function totalPendingXChainWithdraws() external view returns (uint256);
function totalPendingXChainDivests() external view returns (uint256);
function balanceOf(address account, uint256 superformId) external view returns (uint256);
function investSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req)
external
payable
returns (uint256 amount);
function investSingleXChainMultiVault(SingleXChainMultiVaultStateReq calldata req)
external
payable
returns (uint256 totalAmount);
function investMultiXChainSingleVault(MultiDstSingleVaultStateReq calldata req)
external
payable
returns (uint256 totalAmount);
function investMultiXChainMultiVault(MultiDstMultiVaultStateReq calldata req)
external
payable
returns (uint256 totalAmount);
function divestSingleXChainSingleVault(
SingleXChainSingleVaultStateReq calldata req,
bool useReceivers
)
external
payable
returns (uint256 sharesValue);
function divestSingleXChainMultiVault(
SingleXChainMultiVaultStateReq calldata req,
bool useReceivers
)
external
payable
returns (uint256 totalAmount);
function divestMultiXChainSingleVault(
MultiDstSingleVaultStateReq calldata req,
bool useReceivers
)
external
payable
returns (uint256 totalAmount);
function divestMultiXChainMultiVault(
MultiDstMultiVaultStateReq calldata req,
bool useReceivers
)
external
payable
returns (uint256 totalAmount);
function liquidateSingleXChainSingleVault(
uint64 chainId,
uint256 superformId,
uint256 amount,
address receiver,
SingleXChainSingleVaultWithdraw memory config,
uint256 totalRequestedAssets
)
external
payable;
function liquidateSingleXChainMultiVault(
uint64 chainId,
uint256[] memory superformIds,
uint256[] memory amounts,
address receiver,
SingleXChainMultiVaultWithdraw memory config,
uint256 totalRequestedAssets,
uint256[] memory requestedAssetsPerVault
)
external
payable;
function liquidateMultiDstSingleVault(
uint8[][] memory ambIds,
uint64[] memory dstChainIds,
SingleVaultSFData[] memory singleVaultDatas,
uint256[] memory totalRequestedAssets
)
external
payable;
function liquidateMultiDstMultiVault(
uint8[][] memory ambIds,
uint64[] memory dstChainIds,
MultiVaultSFData[] memory multiVaultDatas,
uint256[] memory totalRequestedAssets,
uint256[][] memory requestedAssetsPerVault
)
external
payable;
function supportsInterface(bytes4 interfaceId) external pure returns (bool isSupported);
function onERC1155Received(
address operator,
address from,
uint256 superformId,
uint256 value,
bytes memory data
)
external
returns (bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] memory superformIds,
uint256[] memory values,
bytes memory data
)
external
returns (bytes4);
function settleLiquidation(bytes32 key, bool force) external;
function notifyFailedInvest(uint256 superformId, uint256 refundedAssets) external;
function getReceiver(bytes32 key) external returns (address receiverAddress);
function receivers(bytes32 key) external view returns (address receiverAddress);
function settleDivest(bytes32 key, uint256 assets, bool force) external;
function previewIdDivestSingleXChainSingleVault(SingleXChainSingleVaultStateReq memory req)
external
view
returns (bytes32[] memory requestIds);
function previewIdDivestSingleXChainMultiVault(SingleXChainMultiVaultStateReq memory req)
external
view
returns (bytes32[] memory requestIds);
function previewIdDivestMultiXChainSingleVault(MultiDstSingleVaultStateReq memory req)
external
view
returns (bytes32[] memory requestIds);
function previewIdDivestMultiXChainMultiVault(MultiDstMultiVaultStateReq memory req)
external
view
returns (bytes32[] memory requestIds);
function previewLiquidateSingleXChainSingleVault(
uint64 chainId,
uint256 superformId,
uint256 amount,
address receiver,
SingleXChainSingleVaultWithdraw memory config,
uint256 totalRequestedAssets,
uint256[] memory requestedAssetsPerVault
)
external
view
returns (bytes32[] memory requestIds);
function previewLiquidateSingleXChainMultiVault(
uint64 chainId,
uint256[] memory superformIds,
uint256[] memory amounts,
address receiver,
SingleXChainMultiVaultWithdraw memory config,
uint256 totalRequestedAssets,
uint256[] memory requestedAssetsPerVault
)
external
view
returns (bytes32[] memory requestIds);
function previewLiquidateMultiDstSingleVault(
uint8[][] memory ambIds,
uint64[] memory dstChainIds,
SingleVaultSFData[] memory singleVaultDatas,
uint256[] memory totalRequestedAssets
)
external
view
returns (bytes32[] memory requestIds);
function previewLiquidateMultiDstMultiVault(
uint8[][] memory ambIds,
uint64[] memory dstChainIds,
MultiVaultSFData[] memory multiVaultDatas,
uint256[] memory totalRequestedAssets,
uint256[][] memory totalRequestedAssetsPerVault
)
external
view
returns (bytes32[] memory requestIds);
function addFunction(bytes4, address, bool) external;
function addFunctions(bytes4[] memory, address, bool) external;
function removeFunction(bytes4) external;
function removeFunctions(bytes4[] memory) external;
function requests(bytes32 key) external view returns (address, uint256, address);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ERC7540Engine } from "./ERC7540Engine.sol";
import { ERC7540EngineReader } from "./ERC7540EngineReader.sol";
import { ERC7540EngineSignatures } from "./ERC7540EngineSignatures.sol";
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol";
import { Multicallable } from "solady/utils/Multicallable.sol";
import { SafeCastLib } from "solady/utils/SafeCastLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { MetaVaultBase, MultiFacetProxy } from "common/Lib.sol";
import { IHurdleRateOracle, ISharePriceOracle, ISuperformGateway } from "interfaces/Lib.sol";
import { NoDelegateCall } from "lib/Lib.sol";
import { VaultConfig, VaultData, VaultLib, VaultReport } from "types/Lib.sol";
// XXSSNNNNNNNNSS
// XSEAAAAAAAAAAAAAAAAAAAAAJSS
// XEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJX
// SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX
// XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEN
// SAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAS
// SAAAAAAAAAAAAAAAAAAAAAAA AAAJX NAAAAAAAAAAAAAAAAX
// AAAAAAJ XNAAAAEJJ XS NAAAAAAAJ SEAAAAAAAAAAE AA
// NAAAAAX XSSS X A AAAAAJX SNA NAAAX
// XS EAAAAA XAAX XSEEX ENSJAAAAAAEX N ESXAAAAAJ
// AAAASSSXAAAAAANANAE SES SJENX E SAN ASAAAAAA
// XNJEAJAAAAX AAAAAAAA AJN XNJJJJJJJJSX SSES SEJENXAAAAAAA
// AAENJ XS AAAAAAAANEAN SAAAAAAAAAAAAEEJEAAAEN SAAAAAAAE
// EAN JAAAAAAAA E JAAAAAAAAAAAAAAAAAAENSX NEAAN NAAAAAAAAS
// AAS AAAAAAAAAN JAAAAAAAAAAAAAAAAAAAAAAAAAA JAAJ JAAAAAAAAA
// AA AAAAAAAAAN EAAAAAAAAAAAAAAAAAAS XXSJEAAAAE XAAJX JEEEAAAAAAAAAN
// AAS AAAAAAAAJ NAAAAAAAS XJAAA SJAAAAX SAAJX SAAES NAAAAAAA
// EAAS JAAAAAAAA AAAAAAAX N XNEAAANNAAAASXAA EAAAAA
// AAAE AAAAAAAASSAAAAAAA XX XJAAAAAAAAAAA SJJX JAAAAN
// NAAAJ AAAAAAAAAAAAAAAAA NSXXNNS EAJX XJAAAAAAAA XXXXJE AAAAE
// AAAAEX AAAAAAAA JAAAN SN NJJ A SAAA SN SEAAAAA XXXXXA JAAAA
// SAAAAAJS AAAAAAAX AAAAJ JAA A XAEX SAAAA XXXXXA NAAAA
// AAAAAAAAAAAAAN JAAAAA XJ XXXXXSX SEAAN XXXNJ AAAAE X
// XNAAAAAAAAAAAAAAAAAAANXX NS XJ SAAA XN AAAA JX
// XAAAAAAAA XXAAAAAAA S EAAAN AAAAA AS
// AAAAAA AAAAAAX XXSX XXN EAAJAAANXNAAAAAAE NAX
// XAAAAA AAAAAJ XXXS S XJ NAAE SJNAAAAAAAA AJ
// EAAAAE AAAJ S SSX XEAAE XEAAAAAAAN AA
// AAAAA AAAA XNJ X XSXX XEAAJ NAAAAAAAAA AA
// AAAAA AAAS XNSS XJAAA SEAAAAAAAAA AA
// SAAAAA NAAAJX SJAAAN XEAAAAAAAAAA AA
// SAAAAAX XAAAAAAE XJAAAAE SEAAAAAAAAAAA NSAAN
// XEAAAAA EAAAAAAE NEAAAAX JAAAAAAAAAAAAN SSSNAAAAN
// NAAAAAAX SAAAAAAAAJX NEAAAAEX NAAAAAAAAAAAAAAAAAEJEAAAAAAAAAAAN
// XEAAAAAAASSEAAAAAAAAAAAAAAAAAAAAAJX SEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJSX
// NJAAAAAAAAAAAAAAAAAAAAAAAAENNSSJAAAAAAAAAAAAAAAAAAASSNNNNNNSSSSX
// SJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJX
// XNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS
// SNJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJS
// ASSSNNEAAAAAAAAAAAAAAAAAAAAENX
// X XSSNNNNNNNSSX
/// @title MetaVault
/// @author Unlockd
/// @notice A ERC750 vault implementation for cross-chain yield
/// aggregation
contract MetaVault is MetaVaultBase, Multicallable, NoDelegateCall {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* LIBRARIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Safe casting operations for uint
using SafeCastLib for uint256;
/// @dev Safe transfer operations for ERC20 tokens
using SafeTransferLib for address;
/// @dev Library for vault-related operations
using VaultLib for VaultData;
/// @dev Library for math
using Math for uint256;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when fees are applied to a user
event AssessFees(address indexed controller, uint256 managementFees, uint256 performanceFees, uint256 oracleFees);
/// @dev Emitted when adding a new vault to the portfolio
event AddVault(uint64 indexed chainId, address vault);
/// @dev Emitted when removing a vault from the portfolio
event RemoveVault(uint64 indexed chainId, address vault);
/// @dev Emitted when updating the shares lock time
event SetSharesLockTime(uint24 time);
/// @dev Emitted when updating the management fee
event SetManagementFee(uint16 fee);
/// @dev Emitted when updating the performance fee
event SetPerformanceFee(uint16 fee);
/// @dev Emitted when updating the oracle fee
event SetOracleFee(uint16 fee);
/// @dev Emitted when the emergency shutdown state is changed
event EmergencyShutdown(bool enabled);
/// @dev Emitted when the treasury address is updated
event TreasuryUpdated(address indexed treasury);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Thrown when attempting to rearrange with invalid queue type
error InvalidQueueType();
/// @notice Thrown when new queue order contains duplicate vaults
error DuplicateVaultInOrder();
/// @notice Thrown when new queue order has different number of vaults than current queue
error VaultCountMismatch();
/// @notice Thrown when new queue order is missing vaults from current queue
error MissingVaultFromCurrentQueue();
/// @notice Thrown when new queue order contains vaults not in current queue
error NewVaultNotInCurrentQueue();
/// @notice Thrown when attempting to add a vault that is already listed
error VaultAlreadyListed();
/// @notice Thrown when attempting to withdraw more assets than are currently available
error InsufficientAvailableAssets();
/// @notice Thrown when trying to perform an operation on a request that has not been settled yet
error RequestNotSettled();
/// @notice Thrown when trying to redeem a non processed request
error RedeemNotProcessed();
/// @notice Thrown when attempting to redeem shares that are still locked
error SharesLocked();
/// @notice Thrown when attempting to perform an operation while the vault is in emergency shutdown
error VaultShutdown();
/// @notice Thrown when attempting to to remove a vault that is still in the metavault balance
error SharesBalanceNotZero();
/// @notice Thrown when attempting to operate on an invalid superform ID
error InvalidSuperformId();
/// @notice Thrown when attempting to add a vault with invalid address
error InvalidVaultAddress();
/// @notice Thrown when the maximum queue size is exceeded
error MaxQueueSizeExceeded();
/// @notice Thrown when an invalid zero address is encountered
error InvalidZeroAddress();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Modifier to prevent execution during emergency shutdown
/// @dev Reverts the transaction if emergencyShutdown is true
modifier noEmergencyShutdown() {
if (emergencyShutdown) {
revert VaultShutdown();
}
_;
}
/// @notice Modifier to update the share price water-mark after running a function
/// @dev Updates the high water mark if the current share price exceeds the previous mark
modifier updateGlobalWatermark() {
_;
uint256 sp = sharePrice();
assembly {
let spwm := sload(sharePriceWaterMark.slot)
if lt(spwm, sp) { sstore(sharePriceWaterMark.slot, sp) }
}
}
/// @notice Constructor for the MetaVault contract
/// @param config The initial configuration parameters for the vault
constructor(VaultConfig memory config) MultiFacetProxy(ADMIN_ROLE) {
_asset = config.asset;
_name = config.name;
_symbol = config.symbol;
treasury = config.treasury;
isOperator[treasury][address(this)] = true;
emit OperatorSet(treasury, address(this), true);
managementFee = config.managementFee;
performanceFee = config.performanceFee;
oracleFee = config.oracleFee;
sharesLockTime = config.sharesLockTime;
lastReport = block.timestamp;
_hurdleRateOracle = config.hurdleRateOracle;
// Try to get asset decimals, fallback to default if unsuccessful
(bool success, uint8 result) = _tryGetAssetDecimals(config.asset);
_decimals = success ? result : _DEFAULT_UNDERLYING_DECIMALS;
// Set the chain ID for the current network
THIS_CHAIN_ID = uint64(block.chainid);
// Initialize chainIndexes mapping
for (uint256 i = 0; i != N_CHAINS; i++) {
chainIndexes[DST_CHAINS[i]] = i;
}
lastFeesCharged = block.timestamp;
// Initialize ownership and grant admin role
_initializeOwner(config.owner);
_grantRoles(config.owner, ADMIN_ROLE);
// Initialize signer relayer
signerRelayer = config.signerRelayer;
// Set initial watermark
sharePriceWaterMark = 10 ** decimals();
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC7540 ACTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Transfers assets from sender into the Vault and submits a Request for asynchronous deposit.
/// @param assets the amount of deposit assets to transfer from owner
/// @param controller the controller of the request who will be able to operate the request
/// @param owner the owner of the shares to be deposited
/// @return requestId
function requestDeposit(
uint256 assets,
address controller,
address owner
)
public
override
noDelegateCall
noEmergencyShutdown
returns (uint256 requestId)
{
if (owner != msg.sender) revert InvalidOperator();
requestId = super.requestDeposit(assets, controller, owner);
// fulfill the request directly
_fulfillDepositRequest(controller, assets, convertToShares(assets));
}
/// @notice Mints shares Vault shares to receiver by claiming the Request of the controller.
/// @dev uses msg.sender as controller
/// @param shares to mint
/// @param to shares receiver
/// @return shares minted shares
function deposit(uint256 assets, address to) public override returns (uint256 shares) {
return deposit(assets, to, msg.sender);
}
/// @notice Mints shares Vault shares to receiver by claiming the Request of the controller.
/// @param assets to mint
/// @param to shares receiver
/// @param controller controller address
/// @return shares minted shares
function deposit(
uint256 assets,
address to,
address controller
)
public
override
noDelegateCall
noEmergencyShutdown
returns (uint256 shares)
{
uint256 sharesBalance = balanceOf(to);
shares = super.deposit(assets, to, controller);
// Start shares lock time
_lockShares(controller, sharesBalance, shares);
_afterDeposit(assets, shares);
return shares;
}
/// @notice Mints exactly shares Vault shares to receiver by claiming the Request of the controller.
/// @dev uses msg.sender as controllerisValidSignat
/// @param shares to mint
/// @param to shares receiver
/// @return assets deposited assets
function mint(uint256 shares, address to) public override returns (uint256 assets) {
return mint(shares, to, msg.sender);
}
/// @notice Mints exactly shares Vault shares to receiver by claiming the Request of the controller.
/// @param shares to mint
/// @param to shares receiver
/// @param controller controller address
/// @return assets deposited assets
function mint(
uint256 shares,
address to,
address controller
)
public
override
noDelegateCall
noEmergencyShutdown
returns (uint256 assets)
{
uint256 sharesBalance = balanceOf(to);
assets = super.mint(shares, to, controller);
_lockShares(controller, sharesBalance, shares);
_afterDeposit(assets, shares);
}
/// @dev Override to update the average entry price and the timestamp of last redeem
function _deposit(
uint256 assets,
uint256 shares,
address receiver,
address controller
)
internal
override
returns (uint256 assetsReturn, uint256 sharesReturn)
{
_updatePosition(controller, shares);
if (lastRedeem[controller] == 0) lastRedeem[controller] = block.timestamp;
return super._deposit(assets, shares, receiver, controller);
}
/// @notice Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem.
/// @param shares the amount of shares to be redeemed to transfer from owner
/// @param controller the controller of the request who will be able to operate the request
/// @param owner the source of the shares to be redeemed
/// @return requestId id
function requestRedeem(
uint256 shares,
address controller,
address owner
)
public
override
noDelegateCall
noEmergencyShutdown
returns (uint256 requestId)
{
// Require deposited shares arent locked
_checkSharesLocked(controller);
requestId = super.requestRedeem(shares, controller, owner);
}
/// @dev Redeems shares for assets, ensuring all settled requests are fulfilled
/// @param shares The number of shares to redeem
/// @param receiver The address that will receive the assets
/// @param controller The address that controls the redemption
/// @return assets The amount of assets redeemed
function redeem(
uint256 shares,
address receiver,
address controller
)
public
override
noDelegateCall
nonReentrant
returns (uint256 assets)
{
assets = super.redeem(shares, receiver, controller);
}
/// @dev Withdraws assets, ensuring all settled requests are fulfilled
/// @param assets The amount of assets to withdraw
/// @param receiver The address that will receive the assets
/// @param controller The address that controls the withdrawal
/// @return shares The number of shares burned
function withdraw(
uint256 assets,
address receiver,
address controller
)
public
override
noDelegateCall
nonReentrant
returns (uint256 shares)
{
return super.withdraw(assets, receiver, controller);
}
struct TempWithdrawData {
uint256 entrySharePrice;
uint256 currentSharePrice;
int256 assetsDelta;
uint256 duration;
uint256 performanceFeeExempt;
uint256 managementFeeExempt;
uint256 oracleFeeExempt;
uint256 performanceFees;
uint256 managementFees;
uint256 oracleFees;
uint256 totalFees;
}
/// @dev Override to apply fees on exit
function _withdraw(
uint256 assets,
uint256 shares,
address receiver,
address controller
)
internal
override
returns (uint256 assetsReturn, uint256 sharesReturn)
{
TempWithdrawData memory temp;
// Get the price metrics needed for calculations
temp.entrySharePrice = positions[controller];
temp.currentSharePrice = sharePrice();
// Consume claimable
unchecked {
_claimableRedeemRequest[controller].assets -= assets;
_claimableRedeemRequest[controller].shares -= shares;
}
temp.duration = block.timestamp - Math.max(lastRedeem[controller], lastFeesCharged);
lastRedeem[controller] = block.timestamp;
// Get fee exemptions for this controller
temp.performanceFeeExempt = performanceFeeExempt[controller];
temp.managementFeeExempt = managementFeeExempt[controller];
temp.oracleFeeExempt = oracleFeeExempt[controller];
// Calculate time-based fees (management & oracle)
// These are charged on total assets, prorated for the time period
temp.managementFees =
(assets * temp.duration).fullMulDiv(_sub0(managementFee, temp.managementFeeExempt), SECS_PER_YEAR) / MAX_BPS;
temp.oracleFees =
(assets * temp.duration).fullMulDiv(_sub0(oracleFee, temp.oracleFeeExempt), SECS_PER_YEAR) / MAX_BPS;
assets -= temp.managementFees + temp.oracleFees;
temp.totalFees += temp.managementFees + temp.oracleFees;
// Calculate the asset's value change since entry
// This gives us the raw profit/loss in asset terms
temp.assetsDelta = int256(assets) - (int256(shares) * int256(temp.entrySharePrice) / int256(10 ** decimals()));
// Only calculate fees if there's a profit
if (temp.assetsDelta > 0) {
uint256 totalReturn = uint256(temp.assetsDelta);
// Calculate returns relative to hurdle rate
uint256 hurdleReturn = (assets * hurdleRate()).fullMulDiv(temp.duration, SECS_PER_YEAR) / MAX_BPS;
uint256 excessReturn;
// Only charge performance fees if:
// 1. Current share price is not below
// 2. Returns exceed hurdle rate
if (temp.currentSharePrice > sharePriceWaterMark && totalReturn > hurdleReturn) {
// Only charge performance fees on returns above hurdle rate
excessReturn = totalReturn - hurdleReturn;
temp.performanceFees = excessReturn * _sub0(performanceFee, temp.performanceFeeExempt) / MAX_BPS;
}
// Calculate total fees
temp.totalFees += temp.performanceFees;
}
// Transfer fees to treasury if any were charged
if (temp.totalFees > 0) {
// mint shares for treasury
_mint(treasury, convertToShares(temp.totalFees));
_afterDeposit(temp.totalFees, 0);
}
// Transfer remaining assets to receiver
uint256 totalAssetsAfterFee = assets - temp.totalFees;
asset().safeTransfer(receiver, totalAssetsAfterFee);
{
uint256 managementFees = temp.managementFees;
uint256 performanceFees = temp.performanceFees;
uint256 oracleFees = temp.oracleFees;
// Emit events
/// @solidity memory-safe-assembly
assembly {
let mp := mload(0x40) // Grab the free memory pointer.
// Emit the {Withdraw} event
mstore(0x00, totalAssetsAfterFee)
mstore(0x20, shares)
let m := shr(96, not(0))
log4(
0x00,
0x40,
0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db,
and(m, controller),
and(m, receiver),
and(m, controller)
)
// Emit the {AssessFees} event
mstore(0x00, managementFees)
mstore(0x20, performanceFees)
mstore(0x40, oracleFees)
log2(0x00, 0x60, 0xa443e1db11cb46c65620e8e21d4830a6b9b444fa4c350f0dd0024b8a5a6b6ef5, and(m, controller))
mstore(0x40, mp) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
return (totalAssetsAfterFee, shares);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* VAULT MANAGEMENT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Charges global management, performance, and oracle fees on the vault's total assets
/// @dev Fee charging mechanism works as follows:
/// 1. Time-based fees (management & oracle) are charged on total assets, prorated for the time period
/// 2. Performance fees are only charged if two conditions are met:
/// a) Current share price is above the watermark (high water mark)
/// b) Returns exceed the hurdle rate
/// 3. The hurdle rate is asset-specific:
/// - For stablecoins (e.g., USDC): typically tied to T-Bill yields
/// - For ETH: typically tied to base staking returns (e.g., Lido APY)
/// 4. Performance fees are only charged on excess returns above both:
/// - The watermark (preventing double-charging on same gains)
/// - The hurdle rate (ensuring fees only on excess performance)
/// Example calculation:
/// - If initial assets = $1M, current assets = $1.08M
/// - Duration = 180 days, Management = 2%, Oracle = 0.5%, Performance = 20%
/// - Hurdle = 5% APY
/// Then:
/// 1. Management Fee = $1.08M * 2% * (180/365) = $10,628
/// 2. Oracle Fee = $1.08M * 0.5% * (180/365) = $2,657
/// 3. Hurdle Return = $1M * 5% * (180/365) = $24,657
/// 4. Excess Return = ($80,000 - $13,285 - $24,657) = $42,058
/// 5. Performance Fee = $42,058 * 20% = $8,412
/// @return uint256 Total fees charged
function chargeGlobalFees() external updateGlobalWatermark onlyRoles(MANAGER_ROLE) returns (uint256) {
uint256 currentSharePrice = sharePrice();
uint256 lastSharePrice = sharePriceWaterMark;
uint256 duration = block.timestamp - lastFeesCharged;
uint256 currentTotalAssets = totalAssets();
uint256 lastTotalAssets = totalSupply().fullMulDiv(lastSharePrice, 10 ** decimals());
// Calculate time-based fees (management & oracle)
// These are charged on total assets, prorated for the time period
uint256 managementFees = (currentTotalAssets * duration).fullMulDiv(managementFee, SECS_PER_YEAR) / MAX_BPS;
uint256 oracleFees = (currentTotalAssets * duration).fullMulDiv(oracleFee, SECS_PER_YEAR) / MAX_BPS;
uint256 totalFees = managementFees + oracleFees;
uint256 performanceFees;
currentTotalAssets += managementFees + oracleFees;
lastFeesCharged = block.timestamp;
// Calculate the asset's value change since entry
// This gives us the raw profit/loss in asset terms
int256 assetsDelta = int256(currentTotalAssets) - int256(lastTotalAssets);
// Only calculate fees if there's a profit
if (assetsDelta > 0) {
uint256 excessReturn;
// Calculate returns relative to hurdle rate
uint256 hurdleReturn = (lastTotalAssets * hurdleRate()).fullMulDiv(duration, SECS_PER_YEAR) / MAX_BPS;
uint256 totalReturn = uint256(assetsDelta);
// Only charge performance fees if:
// 1. Current share price is not below
// 2. Returns exceed hurdle rate
if (currentSharePrice > sharePriceWaterMark && totalReturn > hurdleReturn) {
// Only charge performance fees on returns above hurdle rate
excessReturn = totalReturn - hurdleReturn;
performanceFees = excessReturn * performanceFee / MAX_BPS;
}
// Calculate total fees
totalFees += performanceFees;
}
// Transfer fees to treasury if any were charged
if (totalFees > 0) {
_mint(treasury, convertToShares(totalFees));
}
/// @solidity memory-safe-assembly
assembly {
let mp := mload(0x40) // Grab the free memory pointer.
let m := shr(96, not(0))
// Emit the {AssessFees} event
mstore(0x00, managementFees)
mstore(0x20, performanceFees)
mstore(0x40, oracleFees)
log2(0x00, 0x60, 0xa443e1db11cb46c65620e8e21d4830a6b9b444fa4c350f0dd0024b8a5a6b6ef5, and(m, address()))
mstore(0x40, mp) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
return totalFees;
}
/// @notice Add a new vault to the portfolio
/// @param chainId chainId of the vault
/// @param superformId id of superform
/// @param vault vault address
/// @param vaultDecimals decimals of ERC4626 token
/// @param oracle vault shares price oracle
function addVault(
uint32 chainId,
uint256 superformId,
address vault,
uint8 vaultDecimals,
ISharePriceOracle oracle
)
external
onlyRoles(MANAGER_ROLE)
{
if (superformId == 0) revert InvalidSuperformId();
// If its already listed revert
if (isVaultListed(vault)) revert VaultAlreadyListed();
// Save it into storage
vaults[superformId].chainId = chainId;
vaults[superformId].superformId = superformId;
vaults[superformId].vaultAddress = vault;
vaults[superformId].decimals = vaultDecimals;
vaults[superformId].oracle = oracle;
uint192 lastSharePrice = vaults[superformId].sharePrice(asset()).toUint192();
if (lastSharePrice == 0) revert();
_vaultToSuperformId[vault] = superformId;
bool found;
if (chainId == THIS_CHAIN_ID) {
// Push it to the local withdrawal queue
uint256[WITHDRAWAL_QUEUE_SIZE] memory queue = localWithdrawalQueue;
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE; i++) {
if (queue[i] == 0) {
localWithdrawalQueue[i] = superformId;
found = true;
break;
}
}
// If its on the same chain perfom approval to vault
asset().safeApprove(vault, type(uint256).max);
} else {
// Push it to the crosschain withdrawal queue
uint256[WITHDRAWAL_QUEUE_SIZE] memory queue = xChainWithdrawalQueue;
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE; i++) {
if (queue[i] == 0) {
xChainWithdrawalQueue[i] = superformId;
found = true;
break;
}
}
}
if (!found) revert MaxQueueSizeExceeded();
emit AddVault(chainId, vault);
}
/// @notice Remove a vault from the portfolio
/// @param superformId id of vault to be removed
function removeVault(uint256 superformId) external onlyRoles(MANAGER_ROLE) {
if (superformId == 0) revert InvalidSuperformId();
VaultData memory vault = vaults[superformId];
if (vault.convertToAssets(_sharesBalance(vaults[superformId]), asset(), true) > dustThreshold) {
revert SharesBalanceNotZero();
}
uint64 chainId = vault.chainId;
address vaultAddress = vault.vaultAddress;
delete vaults[superformId];
delete _vaultToSuperformId[vaultAddress];
if (chainId == THIS_CHAIN_ID) {
// Remove vault from the local withdrawal queue
uint256[WITHDRAWAL_QUEUE_SIZE] memory queue = localWithdrawalQueue;
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE; i++) {
if (queue[i] == superformId) {
localWithdrawalQueue[i] = 0;
_organizeWithdrawalQueue(localWithdrawalQueue);
break;
}
}
// If its on the same chain revoke approval to vault
asset().safeApprove(vaultAddress, 0);
} else {
// Remove vault from the crosschain withdrawal queue
uint256[WITHDRAWAL_QUEUE_SIZE] memory queue = xChainWithdrawalQueue;
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE; i++) {
if (queue[i] == superformId) {
xChainWithdrawalQueue[i] = 0;
_organizeWithdrawalQueue(xChainWithdrawalQueue);
break;
}
}
}
emit RemoveVault(chainId, vaultAddress);
}
/// @notice Rearranges the withdrawal queue order
/// @param queueType 0 for local queue, 1 for cross-chain queue
/// @param newOrder Array of superformIds in desired order
/// @dev Only callable by MANAGER_ROLE
/// @dev Maintains same vaults but in different order
function rearrangeWithdrawalQueue(
uint8 queueType,
uint256[WITHDRAWAL_QUEUE_SIZE] calldata newOrder
)
external
onlyRoles(MANAGER_ROLE)
{
// Select queue based on type
uint256[WITHDRAWAL_QUEUE_SIZE] storage queue;
if (queueType == 0) {
queue = localWithdrawalQueue;
} else if (queueType == 1) {
queue = xChainWithdrawalQueue;
} else {
revert InvalidQueueType();
}
// Create temporary arrays for validation
uint256[] memory currentVaults = new uint256[](WITHDRAWAL_QUEUE_SIZE);
uint256[] memory newVaults = new uint256[](WITHDRAWAL_QUEUE_SIZE);
uint256 currentCount;
uint256 newCount;
// Collect non-zero vaults from current queue
for (uint256 i = 0; i < WITHDRAWAL_QUEUE_SIZE; i++) {
if (queue[i] != 0) {
currentVaults[currentCount++] = queue[i];
}
}
// Collect non-zero vaults from new order
for (uint256 i = 0; i < WITHDRAWAL_QUEUE_SIZE; i++) {
if (newOrder[i] != 0) {
// Check for duplicates
for (uint256 j = 0; j < newCount; j++) {
if (newVaults[j] == newOrder[i]) revert DuplicateVaultInOrder();
}
newVaults[newCount++] = newOrder[i];
}
}
// Verify same number of non-zero vaults
if (currentCount != newCount) revert VaultCountMismatch();
// Verify all current vaults are in new order
for (uint256 i = 0; i < currentCount; i++) {
bool found = false;
for (uint256 j = 0; j < newCount; j++) {
if (currentVaults[i] == newVaults[j]) {
found = true;
break;
}
}
if (!found) revert MissingVaultFromCurrentQueue();
}
// Update queue with new order
for (uint256 i = 0; i < WITHDRAWAL_QUEUE_SIZE; i++) {
queue[i] = newOrder[i];
}
}
/// @notice Reorganize `withdrawalQueue` based on premise that if there is an
/// empty value between two actual values, then the empty value should be
/// replaced by the later value.
/// @dev Relative ordering of non-zero values is maintained.
function _organizeWithdrawalQueue(uint256[WITHDRAWAL_QUEUE_SIZE] storage queue) internal {
uint256 offset;
for (uint256 i; i < WITHDRAWAL_QUEUE_SIZE;) {
uint256 vault = queue[i];
if (vault == 0) {
unchecked {
++offset;
}
} else if (offset > 0) {
queue[i - offset] = vault;
queue[i] = 0;
}
unchecked {
++i;
}
}
}
/// @notice Allows direct donation of assets to the vault
/// @dev Transfers assets from sender to vault and updates idle balance
/// @param assets The amount of assets to donate
function donate(uint256 assets) external {
asset().safeTransferFrom(msg.sender, address(this), assets);
_afterDeposit(assets, 0);
}
/// @notice Updates the average entry share price of a controller
function _updatePosition(address controller, uint256 mintedShares) internal {
uint256 averageEntryPrice = positions[controller];
uint256 currentSharePrice = sharePrice();
uint256 sharesBalance = balanceOf(controller);
if (averageEntryPrice == 0 || sharesBalance == 0) {
positions[controller] = currentSharePrice;
} else {
uint256 totalCost = sharesBalance * averageEntryPrice + mintedShares * currentSharePrice;
uint256 newTotalAmount = sharesBalance + mintedShares;
uint256 newAverageEntryPrice = totalCost / newTotalAmount;
positions[controller] = newAverageEntryPrice;
}
}
/// @dev Hook that is called after any deposit or mint.
function _afterDeposit(uint256 assets, uint256 /*uint shares*/ ) internal override {
uint128 assetsUint128 = assets.toUint128();
_totalIdle += assetsUint128;
}
/// @dev Reverts if deposited shares are locked
/// @param controller shares controller
function _checkSharesLocked(address controller) private view {
if (block.timestamp < _depositLockCheckPoint[controller] + sharesLockTime) revert SharesLocked();
}
/// @dev Locks the deposited shares for a fixed period
/// @param controller shares receiver
/// @param sharesBalance current shares balance
/// @param newShares newly minted shares
function _lockShares(address controller, uint256 sharesBalance, uint256 newShares) private {
uint256 newBalance = sharesBalance + newShares;
if (sharesBalance == 0) {
_depositLockCheckPoint[controller] = block.timestamp;
} else {
_depositLockCheckPoint[controller] = ((_depositLockCheckPoint[controller] * sharesBalance) / newBalance)
+ ((block.timestamp * newShares) / newBalance);
}
}
/// @dev Supports ERC1155 interface detection
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @return isSupported True if the contract supports the interface, false otherwise
function supportsInterface(bytes4 interfaceId) public pure returns (bool isSupported) {
if (interfaceId == 0x4e2312e0) return true;
}
/// @dev
function convertToSuperPositions(uint256 superformId, uint256 assets) external view returns (uint256) {
return vaults[superformId].convertToShares(assets, asset(), false);
}
/// @notice Handles the receipt of a single ERC1155 token type
/// @dev This function is called at the end of a `safeTransferFrom` after the balance has been updated
/// @param operator The address which initiated the transfer (i.e. msg.sender)
/// @param from The address which previously owned the token
/// @param superformId The ID of the token being transferred
/// @param value The amount of tokens being transferred
/// @param data Additional data with no specified format
/// @return bytes4 `bytes4(keccak256("onERC1155Received(address,address,uint,uint,bytes)"))`
function onERC1155Received(
address operator,
address from,
uint256 superformId,
uint256 value,
bytes memory data
)
public
returns (bytes4)
{
// Silence compiler warnings
operator;
value;
if (msg.sender != address(gateway.superPositions())) revert Unauthorized();
if (data.length > 0 && from == address(gateway)) {
(address controller, uint256 refundedAssets) = abi.decode(data, (address, uint256));
if (refundedAssets != 0) {
_totalDebt += refundedAssets.toUint128();
// Calculate shares corresponding to refundedAssets
uint256 shares = _convertToShares(refundedAssets, totalAssets() - refundedAssets);
if (controller != address(0)) {
// Decrease the pending xchain shares of the user
pendingProcessedShares[controller] = _sub0(pendingProcessedShares[controller], shares);
// Mint back failed shares
_mint(address(this), shares);
}
vaults[superformId].totalDebt += refundedAssets.toUint128();
}
}
return this.onERC1155Received.selector;
}
/// @notice Handles the receipt of multiple ERC1155 token types
/// @dev This function is called at the end of a `safeBatchTransferFrom` after the balances have been updated
/// @param operator The address which initiated the batch transfer (i.e. msg.sender)
/// @param from The address which previously owned the tokens
/// @param superformIds An array containing ids of each token being transferred (order and length must match values
/// array)
/// @param values An array containing amounts of each token being transferred (order and length must match ids
/// array)
/// @param data Additional data with no specified format
/// @return bytes4 `bytes4(keccak256("onERC1155BatchReceived(address,address,uint[],uint[],bytes)"))`
function onERC1155BatchReceived(
address operator,
address from,
uint256[] memory superformIds,
uint256[] memory values,
bytes memory data
)
public
returns (bytes4)
{
// Silence compiler warnings
operator;
values;
data;
superformIds;
if (msg.sender != address(gateway.superPositions())) revert Unauthorized();
if (from != address(gateway)) revert Unauthorized();
return this.onERC1155BatchReceived.selector;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ModuleBase } from "common/Lib.sol";
import { IHurdleRateOracle } from "interfaces/IHurdleRateOracle.sol";
import { ISharePriceOracle } from "interfaces/ISharePriceOracle.sol";
import { ISuperformGateway } from "interfaces/ISuperformGateway.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { VaultData } from "types/Lib.sol";
/// @title MetaVaultAdmin
/// @author Unlockd
/// @notice Admin module for MetaVault that contains all the admin functions
/// @dev This module extracts admin functions from the main MetaVault contract to reduce its size
contract MetaVaultAdmin is ModuleBase {
using SafeTransferLib for address;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Thrown when attempting to set a fee higher than the maximum allowed
error FeeExceedsMaximum();
/// @notice Thrown when attempting to set a shares lock time higher than the maximum allowed
error InvalidSharesLockTime();
/// @notice Thrown when an invalid zero address is encountered
error InvalidZeroAddress();
/// @notice Thrown when an invalid oracle address is provided
error InvalidOracleAddress();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Emitted when the gateway address is updated
event GatewayUpdated(address indexed oldGateway, address indexed newGateway);
/// @notice Emitted when the hurdle rate oracle is updated
event HurdleRateOracleUpdated(address indexed oldOracle, address indexed newOracle);
/// @notice Emitted when the vault oracle is updated
event VaultOracleUpdated(uint256 indexed superformId, address indexed oldOracle, address indexed newOracle);
/// @notice Emitted when updating the shares lock time
event SetSharesLockTime(uint24 time);
/// @notice Emitted when updating the management fee
event SetManagementFee(uint16 fee);
/// @notice Emitted when updating the performance fee
event SetPerformanceFee(uint16 fee);
/// @notice Emitted when updating the oracle fee
event SetOracleFee(uint16 fee);
/// @notice Emitted when the treasury address is updated
event TreasuryUpdated(address indexed treasury);
/// @notice Emitted when the emergency shutdown state is changed
event EmergencyShutdown(bool enabled);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ADMIN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Sets the gateway contract for cross-chain communication
/// @param _gateway The address of the new gateway contract
/// @dev Only callable by addresses with ADMIN_ROLE
function setGateway(ISuperformGateway _gateway) external onlyRoles(ADMIN_ROLE) {
address oldGateway = address(gateway);
if (oldGateway != address(0)) {
gateway.superPositions().setApprovalForAll(oldGateway, false);
asset().safeApprove(oldGateway, 0);
}
gateway = _gateway;
asset().safeApprove(address(_gateway), type(uint256).max);
gateway.superPositions().setApprovalForAll(address(_gateway), true);
emit GatewayUpdated(oldGateway, address(_gateway));
}
/// @notice Sets the hurdle rate oracle for performance fee calculations
/// @param hurdleRateOracle The new oracle address to set
/// @dev Only callable by addresses with ADMIN_ROLE
function setHurdleRateOracle(IHurdleRateOracle hurdleRateOracle) external onlyRoles(ADMIN_ROLE) {
address oldOracle = address(_hurdleRateOracle);
_hurdleRateOracle = hurdleRateOracle;
emit HurdleRateOracleUpdated(oldOracle, address(hurdleRateOracle));
}
/// @notice Sets the oracle for a specific vault
/// @param superformId The ID of the superform to set the oracle for
/// @param oracle The new oracle address to set
/// @dev Only callable by addresses with ADMIN_ROLE
function setVaultOracle(uint256 superformId, ISharePriceOracle oracle) external onlyRoles(ADMIN_ROLE) {
if (address(oracle) == address(0)) revert InvalidOracleAddress();
address oldOracle = address(vaults[superformId].oracle);
vaults[superformId].oracle = oracle;
emit VaultOracleUpdated(superformId, oldOracle, address(oracle));
}
/// @notice Sets the lock time for shares in the vault.
/// @dev Only callable by addresses with ADMIN_ROLE.
/// @param _time The lock time to set for shares, must not exceed MAX_TIME.
function setSharesLockTime(uint24 _time) external onlyRoles(ADMIN_ROLE) {
if (_time > MAX_TIME) revert InvalidSharesLockTime();
sharesLockTime = _time;
emit SetSharesLockTime(_time);
}
/// @notice Sets the treasury address for the vault
/// @dev Only callable by addresses with ADMIN_ROLE
/// @param _treasury The address of the treasury
function setTreasury(address _treasury) external onlyRoles(ADMIN_ROLE) {
if (_treasury == address(0)) revert InvalidZeroAddress();
address oldTreasury = treasury;
treasury = _treasury;
emit TreasuryUpdated(_treasury);
}
/// @notice Sets the annual management fee
/// @param _managementFee New BPS management fee
/// @dev Only callable by addresses with ADMIN_ROLE
function setManagementFee(uint16 _managementFee) external onlyRoles(ADMIN_ROLE) {
if (_managementFee > MAX_FEE) revert FeeExceedsMaximum();
managementFee = _managementFee;
emit SetManagementFee(_managementFee);
}
/// @notice Sets the annual performance fee
/// @param _performanceFee New BPS performance fee
/// @dev Only callable by addresses with ADMIN_ROLE
function setPerformanceFee(uint16 _performanceFee) external onlyRoles(ADMIN_ROLE) {
if (_performanceFee > MAX_FEE) revert FeeExceedsMaximum();
performanceFee = _performanceFee;
emit SetPerformanceFee(_performanceFee);
}
/// @notice Sets the annual oracle fee
/// @param _oracleFee New BPS oracle fee
/// @dev Only callable by addresses with ADMIN_ROLE
function setOracleFee(uint16 _oracleFee) external onlyRoles(ADMIN_ROLE) {
if (_oracleFee > MAX_FEE) revert FeeExceedsMaximum();
oracleFee = _oracleFee;
emit SetOracleFee(_oracleFee);
}
/// @notice Sets custom fee exemptions for specific clients
/// @param controller The address of the client to exempt
/// @param managementFeeExcemption The management fee exemption amount in BPS
/// @param performanceFeeExcemption The performance fee exemption amount in BPS
/// @param oracleFeeExcemption The oracle fee exemption amount in BPS
/// @dev Only callable by addresses with ADMIN_ROLE
function setFeeExcemption(
address controller,
uint256 managementFeeExcemption,
uint256 performanceFeeExcemption,
uint256 oracleFeeExcemption
)
external
onlyRoles(ADMIN_ROLE)
{
performanceFeeExempt[controller] = performanceFeeExcemption;
managementFeeExempt[controller] = managementFeeExcemption;
oracleFeeExempt[controller] = oracleFeeExcemption;
}
/// @notice Sets the emergency shutdown state of the vault
/// @dev Can only be called by addresses with the EMERGENCY_ADMIN_ROLE
/// @param _emergencyShutdown True to enable emergency shutdown, false to disable
function setEmergencyShutdown(bool _emergencyShutdown) external onlyRoles(EMERGENCY_ADMIN_ROLE) {
emergencyShutdown = _emergencyShutdown;
emit EmergencyShutdown(_emergencyShutdown);
}
/// @dev Helper function to fetch module function selectors
function selectors() external pure returns (bytes4[] memory) {
bytes4[] memory s = new bytes4[](10);
s[0] = this.setGateway.selector;
s[1] = this.setHurdleRateOracle.selector;
s[2] = this.setVaultOracle.selector;
s[3] = this.setSharesLockTime.selector;
s[4] = this.setTreasury.selector;
s[5] = this.setManagementFee.selector;
s[6] = this.setPerformanceFee.selector;
s[7] = this.setOracleFee.selector;
s[8] = this.setFeeExcemption.selector;
s[9] = this.setEmergencyShutdown.selector;
return s;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ModuleBase } from "./ModuleBase.sol";
import { MultiFacetProxy } from "./MultiFacetProxy.sol";
/// @title MetaVaultBase
abstract contract MetaVaultBase is ModuleBase, MultiFacetProxy { }
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { ModuleBase } from "common/Lib.sol";
import { ISharePriceOracle } from "../interfaces/ISharePriceOracle.sol";
import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol";
import { VaultData, VaultLib } from "types/Lib.sol";
/// @title MetaVaultReader
/// @notice Module for reading vault data and calculating returns
/// @dev Provides view functions to get detailed vault information and return metrics
contract MetaVaultReader is ModuleBase {
using VaultLib for VaultData;
using FixedPointMathLib for uint256;
/// @notice Detailed data structure for a vault
/// @dev Contains key vault parameters and current state
struct VaultDetailedData {
/// @dev The ID of the chain where the vault is deployed
uint32 chainId;
/// @dev The superform ID of the vault in the Superform protocol
uint256 superformId;
/// @dev The oracle that provides the share price for the vault
ISharePriceOracle oracle;
/// @dev The number of decimals used in the ERC4626 shares
uint8 decimals;
/// @dev The total assets invested in the vault
uint128 totalDebt;
/// @dev The address of the vault
address vaultAddress;
/// @dev Owned vault shares
uint256 sharesBalance;
/// @dev Share price of the vault
uint256 sharePrice;
/// @dev Total assets managed by vault
uint256 totalAssets;
}
/// @notice Gets detailed data for a specific vault
/// @param superformId The ID of the vault to query
/// @return VaultDetailedData struct containing vault details
function getVaultDetailedData(uint256 superformId) public view returns (VaultDetailedData memory) {
VaultData memory vault = vaults[superformId];
uint256 sharesBalance = _sharesBalance(vault);
uint256 totalAssets = vault.convertToAssets(sharesBalance, asset(), true);
uint256 sharePrice = vault.sharePrice(asset());
return VaultDetailedData(
vault.chainId,
vault.superformId,
vault.oracle,
vault.decimals,
vault.totalDebt,
vault.vaultAddress,
sharesBalance,
sharePrice,
totalAssets
);
}
/// @notice Gets detailed data for all vaults in both withdrawal queues
/// @return Array of VaultDetailedData structs containing details for all vaults
function getAllVaultsDetailedData() public view returns (VaultDetailedData[] memory) {
uint256 localCount;
uint256 xChainCount;
// Count non-empty vaults in both queues
for (uint256 i = 0; i < WITHDRAWAL_QUEUE_SIZE; i++) {
if (vaults[localWithdrawalQueue[i]].vaultAddress != address(0)) localCount++;
if (vaults[xChainWithdrawalQueue[i]].vaultAddress != address(0)) xChainCount++;
}
VaultDetailedData[] memory allVaultData = new VaultDetailedData[](localCount + xChainCount);
uint256 currentIndex;
// Add local withdrawal queue vaults
for (uint256 i = 0; i < localCount; i++) {
allVaultData[currentIndex] = getVaultDetailedData(localWithdrawalQueue[i]);
currentIndex++;
}
// Add cross-chain withdrawal queue vaults
for (uint256 i = 0; i < xChainCount; i++) {
allVaultData[currentIndex] = getVaultDetailedData(xChainWithdrawalQueue[i]);
currentIndex++;
}
return allVaultData;
}
/// @notice Data structure for vault return metrics
/// @dev Contains total, hurdle and excess returns
struct VaultReturnsData {
/// @dev Total return generated by the vault
uint256 totalReturn;
/// @dev Minimum return required before performance fees
uint256 hurdleReturn;
/// @dev Returns above the hurdle rate
uint256 excessReturn;
}
/// @notice Calculates return metrics for the metavault
/// @return VaultReturnsData struct containing return metrics
/// @dev Calculates total return, hurdle return and excess return based on asset value changes
function getLastEpochVaultReturns() public view returns (VaultReturnsData memory) {
uint256 currentSharePrice = sharePrice();
uint256 lastSharePrice = sharePriceWaterMark;
uint256 duration = block.timestamp - lastFeesCharged;
uint256 currentTotalAssets = totalAssets();
uint256 lastTotalAssets = totalSupply().fullMulDiv(lastSharePrice, 10 ** decimals());
// Calculate returns relative to hurdle rate
uint256 hurdleReturn = (lastTotalAssets * hurdleRate()).fullMulDiv(duration, SECS_PER_YEAR) / MAX_BPS;
int256 assetsDelta = int256(currentTotalAssets) - int256(lastTotalAssets);
uint256 totalReturn = assetsDelta > 0 ? uint256(assetsDelta) : 0;
uint256 excessReturn = 0;
// Only calculate excess return if total return exceeds hurdle return and price is above watermark
if (totalReturn > hurdleReturn && currentSharePrice > sharePriceWaterMark) {
excessReturn = totalReturn - hurdleReturn;
}
return VaultReturnsData(totalReturn, hurdleReturn, excessReturn);
}
/// @notice Calculates the total returns per share since inception
/// @return The difference between current share price and initial share price (1e18)
/// @dev Returns can be negative if share price is below initial price
function totalReturnsPerShare() public view returns (int256) {
return int256(sharePrice()) - int256(10 ** decimals());
}
/// @notice Returns the function selectors that this module exposes
/// @return selectors Array of function selectors
function selectors() external pure returns (bytes4[] memory) {
bytes4[] memory _selectors = new bytes4[](4);
_selectors[0] = this.getVaultDetailedData.selector;
_selectors[1] = this.getAllVaultsDetailedData.selector;
_selectors[2] = this.getLastEpochVaultReturns.selector;
_selectors[3] = this.totalReturnsPerShare.selector;
return _selectors;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { IHurdleRateOracle } from "../interfaces/IHurdleRateOracle.sol";
import { ISuperformGateway } from "../interfaces/ISuperformGateway.sol";
import { ERC7540, ReentrancyGuard } from "lib/Lib.sol";
import { OwnableRoles } from "solady/auth/OwnableRoles.sol";
import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { VaultData, VaultLib } from "types/Lib.sol";
/// @title ModuleBase Contract for MetaVault Modules
/// @author Unlockd
/// @notice Base storage contract containing all shared state variables and helper functions for MetaVault modules
/// @dev Implements role-based access control and core vault functionality
contract ModuleBase is OwnableRoles, ERC7540, ReentrancyGuard {
using VaultLib for VaultData;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Maximum size of the withdrawal queue
uint256 public constant WITHDRAWAL_QUEUE_SIZE = 30;
/// @notice Number of seconds in a year, used for APY calculations
uint256 public constant SECS_PER_YEAR = 31_556_952;
/// @notice Maximum basis points (100%)
uint256 public constant MAX_BPS = 10_000;
/// @notice Role identifier for admin privileges
uint256 public constant ADMIN_ROLE = _ROLE_0;
/// @notice Role identifier for emergency admin privileges
uint256 public constant EMERGENCY_ADMIN_ROLE = _ROLE_1;
/// @notice Role identifier for oracle privileges
uint256 public constant ORACLE_ROLE = _ROLE_2;
/// @notice Role identifier for manager privileges
uint256 public constant MANAGER_ROLE = _ROLE_3;
/// @notice Role identifier for relayer privileges
uint256 public constant RELAYER_ROLE = _ROLE_4;
/// @notice Chain ID of the current network
uint64 public THIS_CHAIN_ID;
/// @notice Number of supported chains
uint256 public constant N_CHAINS = 7;
/// @dev Maximum fee that can be set (100% = 10000 basis points)
uint16 constant MAX_FEE = 10_000;
/// @dev Maximum time that can be set (48 hours)
uint256 public MAX_TIME = 172_800;
/// @notice Nonce slot seed
uint256 internal constant _NONCES_SLOT_SEED = 0x38377508;
/// @notice mapping from address to the average share price of their deposits
mapping(address => uint256 averageEntryPrice) public positions;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @notice Cached value of total assets in this vault
uint128 internal _totalIdle;
/// @notice Cached value of total allocated assets
uint128 internal _totalDebt;
/// @notice Asset decimals
uint8 _decimals;
/// @notice Protocol fee
uint16 public managementFee;
/// @notice Protocol fee
uint16 public performanceFee;
/// @notice Protocol fee
uint16 public oracleFee;
/// @notice Minimum time users must wait to redeem shares
uint24 public sharesLockTime;
/// @notice Wether the vault is paused
bool public emergencyShutdown;
/// @notice Fee receiver
address public treasury;
/// @notice Underlying asset
address internal _asset;
/// @notice Signer address to process redeem requests
address public signerRelayer;
/// @notice Gateway contract to interact with superform
ISuperformGateway public gateway;
/// @notice ERC20 name
string internal _name;
/// @notice ERC20 symbol
string internal _symbol;
/// @notice maps the assets and data of each allocated vault
/// @notice Vaults portfolio on this same chain
uint256[WITHDRAWAL_QUEUE_SIZE] public localWithdrawalQueue;
/// @notice Vaults portfolio in external chains
uint256[WITHDRAWAL_QUEUE_SIZE] public xChainWithdrawalQueue;
/// @notice Hurdle rate of underlying asset
IHurdleRateOracle internal _hurdleRateOracle;
/// @notice Timestamp of last report
uint256 public lastReport;
/// @notice Timestamp when fees were last charged globally
uint256 public lastFeesCharged;
/// @notice The ATH share price
uint256 public sharePriceWaterMark;
/// @notice The amount of assets to be considered dust by the protocol
uint256 public dustThreshold;
/// @notice Array of destination chain IDs
/// @dev Includes Ethereum Mainnet, Polygon, BNB Chain, Optimism, Base, Arbitrum One, and Avalanche
uint64[N_CHAINS] public DST_CHAINS = [
1, // Ethereum Mainnet
137, // Polygon
56, // BNB Chain
10, // Optimism
8453, // Base
42_161, // Arbitrum One
43_114 // Avalanche
];
/// @notice Timestamp of deposit lock
mapping(address => uint256) internal _depositLockCheckPoint;
/// @notice Storage of each vault related data
mapping(uint256 => VaultData) public vaults;
/// @notice Inverse mapping vault => superformId
mapping(address => uint256) _vaultToSuperformId;
/// @notice Nonce of each controller
mapping(address controller => uint256 nonce) internal _controllerNonces;
/// @notice Mapping of chain IDs to their respective indexes
mapping(uint64 => uint256) internal chainIndexes;
/// @notice Custom performance fee exemptions per controller
mapping(address controller => uint256) public performanceFeeExempt;
/// @notice Custom management fee exemptions per controller
mapping(address controller => uint256) public managementFeeExempt;
/// @notice Custom oracle fee exemptions per controller
mapping(address controller => uint256) public oracleFeeExempt;
/// @notice Timestamp of last redemption per controller
mapping(address controller => uint256) public lastRedeem;
/// @notice Number of shares that are pending to be settled;
mapping(address controller => uint256) public pendingProcessedShares;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HELPR FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Private helper to substract a - b or return 0 if it underflows
function _sub0(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
return a - b > a ? 0 : a - b;
}
}
/// @notice Gets the shares balance of a vault in the portfolio
/// @param data The vault data structure containing chain ID and address information
/// @return shares The number of shares held in the vault
/// @dev For same-chain vaults, fetches directly from the vault; for cross-chain vaults, uses Superform ERC1155
function _sharesBalance(VaultData memory data) internal view returns (uint256 shares) {
if (data.chainId == THIS_CHAIN_ID) {
return ERC4626(data.vaultAddress).balanceOf(address(this));
} else {
return gateway.balanceOf(address(this), data.superformId);
}
}
/// @dev Private helper to return `x + 1` without the overflow check.
/// Used for computing the denominator input to `FixedPointMathLib.fullMulDiv(a, b, x + 1)`.
/// When `x == type(uint).max`, we get `x + 1 == 0` (mod 2**256 - 1),
/// and `FixedPointMathLib.fullMulDiv` will revert as the denominator is zero.
function _inc_(uint256 x) internal pure returns (uint256) {
unchecked {
return x + 1;
}
}
/// @dev Private helper to return if either value is zero.
function _eitherIsZero_(uint256 a, uint256 b) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := or(iszero(a), iszero(b))
}
}
/// @dev Private helper get an array uint full of zeros
/// @param len array length
/// @return
function _getEmptyuintArray(uint256 len) internal pure returns (uint256[] memory) {
return new uint256[](len);
}
/// @notice the number of decimals of the underlying token
function _underlyingDecimals() internal view override returns (uint8) {
return _decimals;
}
/// @notice Converts a fixed-size array to a dynamic array
/// @param arr The fixed-size array to convert
/// @param len The length of the new dynamic array
/// @return dynArr The converted dynamic array
/// @dev Used to prepare data for cross-chain transactions
function _toDynamicUint256Array(
uint256[WITHDRAWAL_QUEUE_SIZE] memory arr,
uint256 len
)
internal
pure
returns (uint256[] memory dynArr)
{
dynArr = new uint256[](len);
for (uint256 i = 0; i < len; ++i) {
dynArr[i] = arr[i];
}
}
/// @dev Helper function to get a empty bools array
function _getEmptyBoolArray(uint256 len) internal pure returns (bool[] memory) {
return new bool[](len);
}
/// @dev Private helper to calculate shares from any @param _totalAssets
function _convertToShares(uint256 assets, uint256 _totalAssets) internal view returns (uint256 shares) {
if (!_useVirtualShares()) {
uint256 supply = totalSupply();
return _eitherIsZero_(assets, supply)
? _initialConvertToShares(assets)
: Math.fullMulDiv(assets, supply, _totalAssets);
}
uint256 o = _decimalsOffset();
if (o == uint256(0)) {
return Math.fullMulDiv(assets, totalSupply() + 1, _inc_(_totalAssets));
}
return Math.fullMulDiv(assets, totalSupply() + 10 ** o, _inc_(_totalAssets));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONTEXT GETTERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function name() public view override returns (string memory) {
return _name;
}
/// @notice Returns the symbol of the token.
function symbol() public view override returns (string memory) {
return _symbol;
}
/// @notice Returns the estimate price of 1 vault share
function sharePrice() public view returns (uint256) {
return convertToAssets(10 ** decimals());
}
/// @notice Returns the base hurdle rate for performance fee calculations
/// @dev The hurdle rate differs by asset:
/// - For stablecoins (USDC): Typically set to T-Bills yield (e.g., 5.5% APY)
/// - For ETH: Typically set to base staking return like Lido (e.g., 3.5% APY)
/// @return uint256 The current base hurdle rate in basis points
function hurdleRate() public view returns (uint256) {
return _hurdleRateOracle.getRate(asset());
}
/// @notice helper function to see if a vault is listed
function isVaultListed(address vaultAddress) public view returns (bool) {
return _vaultToSuperformId[vaultAddress] != 0;
}
/// @notice helper function to see if a vault is listed
function isVaultListed(uint256 superformId) public view returns (bool) {
return vaults[superformId].vaultAddress != address(0);
}
/// @notice returns the struct containtaining vault data
function getVault(uint256 superformId) public view returns (VaultData memory vault) {
return vaults[superformId];
}
/// @notice Returns the address of the underlying asset.
function asset() public view override returns (address) {
return _asset;
}
/// @notice Returns the total amount of the underlying asset managed by the Vault.
function totalAssets() public view override returns (uint256 assets) {
return gateway.totalpendingXChainInvests() + gateway.totalPendingXChainDivests() + totalWithdrawableAssets();
}
/// @notice Returns the total amount of the underlying asset that have been deposited into the vault.
function totalDeposits() public view returns (uint256 assets) {
return totalIdle() + totalDebt();
}
/// @notice Returns the total amount of the underlying assets that are settled.
function totalWithdrawableAssets() public view returns (uint256 assets) {
return totalLocalAssets() + totalXChainAssets();
}
/// @notice Returns the total amount of the underlying asset that are located on this
/// same chain and can be transferred synchronously
function totalLocalAssets() public view returns (uint256 assets) {
assets = _totalIdle;
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE;) {
VaultData memory vault = vaults[localWithdrawalQueue[i]];
if (vault.vaultAddress == address(0)) break;
assets += vault.convertToAssets(_sharesBalance(vault), asset(), false);
++i;
}
return assets;
}
/// @notice Returns the total amount of the underlying asset that are located on
/// other chains and need asynchronous transfers
function totalXChainAssets() public view returns (uint256 assets) {
for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE;) {
VaultData memory vault = vaults[xChainWithdrawalQueue[i]];
if (vault.vaultAddress == address(0)) break;
assets += vault.convertToAssets(_sharesBalance(vault), asset(), false);
++i;
}
return assets;
}
/// @notice returns the assets that are sitting idle in this contract
/// @return assets amount of idle assets
function totalIdle() public view returns (uint256 assets) {
return _totalIdle;
}
/// @notice returns the total issued debt of underlying vaulrs
/// @return assets amount assets that are invested in vaults
function totalDebt() public view returns (uint256 assets) {
return _totalDebt;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { Proxy } from "openzeppelin-contracts/proxy/Proxy.sol";
import { OwnableRoles } from "solady/auth/OwnableRoles.sol";
/// @title MultiFacetProxy
/// @notice A proxy contract that can route function calls to different implementation contracts
/// @dev Inherits from Base and OpenZeppelin's Proxy contract
contract MultiFacetProxy is Proxy, OwnableRoles {
/// @notice Mapping of chain method selectors to implementation contracts
mapping(bytes4 => address) selectorToImplementation;
// 0x4fa563f6ad0f2ba943d6492a5a9c8ec6e039cc68444fb93b0b51ea1d78a61ef8 = keccak256("MultiFacetProxy")
constructor(uint256 _proxyAdminRole) {
assembly {
sstore(0x4fa563f6ad0f2ba943d6492a5a9c8ec6e039cc68444fb93b0b51ea1d78a61ef8, _proxyAdminRole)
}
}
function _proxyAdminRole() internal view returns (uint256 role) {
assembly {
role := sload(0x4fa563f6ad0f2ba943d6492a5a9c8ec6e039cc68444fb93b0b51ea1d78a61ef8)
}
}
/// @notice Adds a function selector mapping to an implementation address
/// @param selector The function selector to add
/// @param implementation The implementation contract address
/// @param forceOverride If true, allows overwriting existing mappings
/// @dev Only callable by admin role
function addFunction(
bytes4 selector,
address implementation,
bool forceOverride
)
public
onlyRoles(_proxyAdminRole())
{
if (!forceOverride) {
if (selectorToImplementation[selector] != address(0)) revert();
}
selectorToImplementation[selector] = implementation;
}
/// @notice Adds multiple function selector mappings to an implementation
/// @param selectors Array of function selectors to add
/// @param implementation The implementation contract address
/// @param forceOverride If true, allows overwriting existing mappings
function addFunctions(bytes4[] calldata selectors, address implementation, bool forceOverride) public {
for (uint256 i = 0; i < selectors.length; i++) {
addFunction(selectors[i], implementation, forceOverride);
}
}
/// @notice Removes a function selector mapping
/// @param selector The function selector to remove
/// @dev Only callable by admin role
function removeFunction(bytes4 selector) public onlyRoles(_proxyAdminRole()) {
delete selectorToImplementation[selector];
}
/// @notice Removes multiple function selector mappings
/// @param selectors Array of function selectors to remove
function removeFunctions(bytes4[] calldata selectors) public {
for (uint256 i = 0; i < selectors.length; i++) {
removeFunction(selectors[i]);
}
}
/// @notice Returns the implementation address for a function selector
/// @dev Required override from OpenZeppelin Proxy contract
/// @return The implementation contract address
function _implementation() internal view override returns (address) {
bytes4 selector = msg.sig;
address implementation = selectorToImplementation[selector];
if (implementation == address(0)) revert();
return implementation;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Contract that enables a single call to call multiple methods on itself.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Multicallable.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Multicallable.sol)
///
/// WARNING:
/// This implementation is NOT to be used with ERC2771 out-of-the-box.
/// https://blog.openzeppelin.com/arbitrary-address-spoofing-vulnerability-erc2771context-multicall-public-disclosure
/// This also applies to potentially other ERCs / patterns appending to the back of calldata.
///
/// We do NOT have a check for ERC2771, as we do not inherit from OpenZeppelin's context.
/// Moreover, it is infeasible and inefficient for us to add checks and mitigations
/// for all possible ERC / patterns appending to the back of calldata.
///
/// We would highly recommend using an alternative pattern such as
/// https://github.com/Vectorized/multicaller
/// which is more flexible, futureproof, and safer by default.
abstract contract Multicallable {
/// @dev Apply `DELEGATECALL` with the current contract to each calldata in `data`,
/// and store the `abi.encode` formatted results of each `DELEGATECALL` into `results`.
/// If any of the `DELEGATECALL`s reverts, the entire context is reverted,
/// and the error is bubbled up.
///
/// This function is deliberately made non-payable to guard against double-spending.
/// (See: https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong)
///
/// For efficiency, this function will directly return the results, terminating the context.
/// If called internally, it must be called at the end of a function
/// that returns `(bytes[] memory)`.
function multicall(bytes[] calldata data) public virtual returns (bytes[] memory) {
assembly {
mstore(0x00, 0x20)
mstore(0x20, data.length) // Store `data.length` into `results`.
// Early return if no data.
if iszero(data.length) { return(0x00, 0x40) }
let results := 0x40
// `shl` 5 is equivalent to multiplying by 0x20.
let end := shl(5, data.length)
// Copy the offsets from calldata into memory.
calldatacopy(0x40, data.offset, end)
// Offset into `results`.
let resultsOffset := end
// Pointer to the end of `results`.
end := add(results, end)
for {} 1 {} {
// The offset of the current bytes in the calldata.
let o := add(data.offset, mload(results))
let m := add(resultsOffset, 0x40)
// Copy the current bytes from calldata to the memory.
calldatacopy(
m,
add(o, 0x20), // The offset of the current bytes' bytes.
calldataload(o) // The length of the current bytes.
)
if iszero(delegatecall(gas(), address(), m, calldataload(o), codesize(), 0x00)) {
// Bubble up the revert if the delegatecall reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Append the current `resultsOffset` into `results`.
mstore(results, resultsOffset)
results := add(results, 0x20)
// Append the `returndatasize()`, and the return data.
mstore(m, returndatasize())
returndatacopy(add(m, 0x20), 0x00, returndatasize())
// Advance the `resultsOffset` by `returndatasize() + 0x20`,
// rounded up to the next multiple of 32.
resultsOffset :=
and(add(add(resultsOffset, returndatasize()), 0x3f), 0xffffffffffffffe0)
if iszero(lt(results, end)) { break }
}
return(0x00, add(resultsOffset, 0x40))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Prevents delegatecall to a contract
/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract
abstract contract NoDelegateCall {
error DelegateCallNotAllowed();
constructor() {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
// 0xa203c8cf3ff5695cb1e2caee14320584cc3e0e4b039b8fa3ae49b5e0568c699d = keccak256("NoDelegateCall::original")
assembly {
sstore(0xa203c8cf3ff5695cb1e2caee14320584cc3e0e4b039b8fa3ae49b5e0568c699d, address())
}
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function checkNotDelegateCall() private view {
if (address(this) != _getOriginalAddress()) revert DelegateCallNotAllowed();
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
function _getOriginalAddress() private view returns (address original) {
assembly {
original := sload(0xa203c8cf3ff5695cb1e2caee14320584cc3e0e4b039b8fa3ae49b5e0568c699d)
}
}
/// @notice Prevents delegatecall into the modified method
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Ownable} from "./Ownable.sol";
/// @notice Simple single owner and multiroles authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/OwnableRoles.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract OwnableRoles is Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The `user`'s roles is updated to `roles`.
/// Each bit of `roles` represents whether the role is set.
event RolesUpdated(address indexed user, uint256 indexed roles);
/// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`.
uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE =
0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The role slot of `user` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED))
/// let roleSlot := keccak256(0x00, 0x20)
/// ```
/// This automatically ignores the upper bits of the `user` in case
/// they are not clean, as well as keep the `keccak256` under 32-bytes.
///
/// Note: This is equivalent to `uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))`.
uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Overwrite the roles directly without authorization guard.
function _setRoles(address user, uint256 roles) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Store the new value.
sstore(keccak256(0x0c, 0x20), roles)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles)
}
}
/// @dev Updates the roles directly without authorization guard.
/// If `on` is true, each set bit of `roles` will be turned on,
/// otherwise, each set bit of `roles` will be turned off.
function _updateRoles(address user, uint256 roles, bool on) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
let roleSlot := keccak256(0x0c, 0x20)
// Load the current value.
let current := sload(roleSlot)
// Compute the updated roles if `on` is true.
let updated := or(current, roles)
// Compute the updated roles if `on` is false.
// Use `and` to compute the intersection of `current` and `roles`,
// `xor` it with `current` to flip the bits in the intersection.
if iszero(on) { updated := xor(current, and(current, roles)) }
// Then, store the new value.
sstore(roleSlot, updated)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated)
}
}
/// @dev Grants the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn on.
function _grantRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, true);
}
/// @dev Removes the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn off.
function _removeRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, false);
}
/// @dev Throws if the sender does not have any of the `roles`.
function _checkRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Throws if the sender is not the owner,
/// and does not have any of the `roles`.
/// Checks for ownership first, then lazily checks for roles.
function _checkOwnerOrRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Throws if the sender does not have any of the `roles`,
/// and is not the owner.
/// Checks for roles first, then lazily checks for ownership.
function _checkRolesOrOwner(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } {
// We don't need to mask the values of `ordinals`, as Solidity
// cleans dirty upper bits when storing variables into memory.
roles := or(shl(mload(add(ordinals, i)), 1), roles)
}
}
}
/// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) {
/// @solidity memory-safe-assembly
assembly {
// Grab the pointer to the free memory.
ordinals := mload(0x40)
let ptr := add(ordinals, 0x20)
let o := 0
// The absence of lookup tables, De Bruijn, etc., here is intentional for
// smaller bytecode, as this function is not meant to be called on-chain.
for { let t := roles } 1 {} {
mstore(ptr, o)
// `shr` 5 is equivalent to multiplying by 0x20.
// Push back into the ordinals array if the bit is set.
ptr := add(ptr, shl(5, and(t, 1)))
o := add(o, 1)
t := shr(o, roles)
if iszero(t) { break }
}
// Store the length of `ordinals`.
mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20))))
// Allocate the memory.
mstore(0x40, ptr)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to grant `user` `roles`.
/// If the `user` already has a role, then it will be an no-op for the role.
function grantRoles(address user, uint256 roles) public payable virtual onlyOwner {
_grantRoles(user, roles);
}
/// @dev Allows the owner to remove `user` `roles`.
/// If the `user` does not have a role, then it will be an no-op for the role.
function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner {
_removeRoles(user, roles);
}
/// @dev Allow the caller to remove their own roles.
/// If the caller does not have a role, then it will be an no-op for the role.
function renounceRoles(uint256 roles) public payable virtual {
_removeRoles(msg.sender, roles);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the roles of `user`.
function rolesOf(address user) public view virtual returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Load the stored value.
roles := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns whether `user` has any of `roles`.
function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles != 0;
}
/// @dev Returns whether `user` has all of `roles`.
function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles == roles;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by an account with `roles`.
modifier onlyRoles(uint256 roles) virtual {
_checkRoles(roles);
_;
}
/// @dev Marks a function as only callable by the owner or by an account
/// with `roles`. Checks for ownership first, then lazily checks for roles.
modifier onlyOwnerOrRoles(uint256 roles) virtual {
_checkOwnerOrRoles(roles);
_;
}
/// @dev Marks a function as only callable by an account with `roles`
/// or the owner. Checks for roles first, then lazily checks for ownership.
modifier onlyRolesOrOwner(uint256 roles) virtual {
_checkRolesOrOwner(roles);
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ROLE CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// IYKYK
uint256 internal constant _ROLE_0 = 1 << 0;
uint256 internal constant _ROLE_1 = 1 << 1;
uint256 internal constant _ROLE_2 = 1 << 2;
uint256 internal constant _ROLE_3 = 1 << 3;
uint256 internal constant _ROLE_4 = 1 << 4;
uint256 internal constant _ROLE_5 = 1 << 5;
uint256 internal constant _ROLE_6 = 1 << 6;
uint256 internal constant _ROLE_7 = 1 << 7;
uint256 internal constant _ROLE_8 = 1 << 8;
uint256 internal constant _ROLE_9 = 1 << 9;
uint256 internal constant _ROLE_10 = 1 << 10;
uint256 internal constant _ROLE_11 = 1 << 11;
uint256 internal constant _ROLE_12 = 1 << 12;
uint256 internal constant _ROLE_13 = 1 << 13;
uint256 internal constant _ROLE_14 = 1 << 14;
uint256 internal constant _ROLE_15 = 1 << 15;
uint256 internal constant _ROLE_16 = 1 << 16;
uint256 internal constant _ROLE_17 = 1 << 17;
uint256 internal constant _ROLE_18 = 1 << 18;
uint256 internal constant _ROLE_19 = 1 << 19;
uint256 internal constant _ROLE_20 = 1 << 20;
uint256 internal constant _ROLE_21 = 1 << 21;
uint256 internal constant _ROLE_22 = 1 << 22;
uint256 internal constant _ROLE_23 = 1 << 23;
uint256 internal constant _ROLE_24 = 1 << 24;
uint256 internal constant _ROLE_25 = 1 << 25;
uint256 internal constant _ROLE_26 = 1 << 26;
uint256 internal constant _ROLE_27 = 1 << 27;
uint256 internal constant _ROLE_28 = 1 << 28;
uint256 internal constant _ROLE_29 = 1 << 29;
uint256 internal constant _ROLE_30 = 1 << 30;
uint256 internal constant _ROLE_31 = 1 << 31;
uint256 internal constant _ROLE_32 = 1 << 32;
uint256 internal constant _ROLE_33 = 1 << 33;
uint256 internal constant _ROLE_34 = 1 << 34;
uint256 internal constant _ROLE_35 = 1 << 35;
uint256 internal constant _ROLE_36 = 1 << 36;
uint256 internal constant _ROLE_37 = 1 << 37;
uint256 internal constant _ROLE_38 = 1 << 38;
uint256 internal constant _ROLE_39 = 1 << 39;
uint256 internal constant _ROLE_40 = 1 << 40;
uint256 internal constant _ROLE_41 = 1 << 41;
uint256 internal constant _ROLE_42 = 1 << 42;
uint256 internal constant _ROLE_43 = 1 << 43;
uint256 internal constant _ROLE_44 = 1 << 44;
uint256 internal constant _ROLE_45 = 1 << 45;
uint256 internal constant _ROLE_46 = 1 << 46;
uint256 internal constant _ROLE_47 = 1 << 47;
uint256 internal constant _ROLE_48 = 1 << 48;
uint256 internal constant _ROLE_49 = 1 << 49;
uint256 internal constant _ROLE_50 = 1 << 50;
uint256 internal constant _ROLE_51 = 1 << 51;
uint256 internal constant _ROLE_52 = 1 << 52;
uint256 internal constant _ROLE_53 = 1 << 53;
uint256 internal constant _ROLE_54 = 1 << 54;
uint256 internal constant _ROLE_55 = 1 << 55;
uint256 internal constant _ROLE_56 = 1 << 56;
uint256 internal constant _ROLE_57 = 1 << 57;
uint256 internal constant _ROLE_58 = 1 << 58;
uint256 internal constant _ROLE_59 = 1 << 59;
uint256 internal constant _ROLE_60 = 1 << 60;
uint256 internal constant _ROLE_61 = 1 << 61;
uint256 internal constant _ROLE_62 = 1 << 62;
uint256 internal constant _ROLE_63 = 1 << 63;
uint256 internal constant _ROLE_64 = 1 << 64;
uint256 internal constant _ROLE_65 = 1 << 65;
uint256 internal constant _ROLE_66 = 1 << 66;
uint256 internal constant _ROLE_67 = 1 << 67;
uint256 internal constant _ROLE_68 = 1 << 68;
uint256 internal constant _ROLE_69 = 1 << 69;
uint256 internal constant _ROLE_70 = 1 << 70;
uint256 internal constant _ROLE_71 = 1 << 71;
uint256 internal constant _ROLE_72 = 1 << 72;
uint256 internal constant _ROLE_73 = 1 << 73;
uint256 internal constant _ROLE_74 = 1 << 74;
uint256 internal constant _ROLE_75 = 1 << 75;
uint256 internal constant _ROLE_76 = 1 << 76;
uint256 internal constant _ROLE_77 = 1 << 77;
uint256 internal constant _ROLE_78 = 1 << 78;
uint256 internal constant _ROLE_79 = 1 << 79;
uint256 internal constant _ROLE_80 = 1 << 80;
uint256 internal constant _ROLE_81 = 1 << 81;
uint256 internal constant _ROLE_82 = 1 << 82;
uint256 internal constant _ROLE_83 = 1 << 83;
uint256 internal constant _ROLE_84 = 1 << 84;
uint256 internal constant _ROLE_85 = 1 << 85;
uint256 internal constant _ROLE_86 = 1 << 86;
uint256 internal constant _ROLE_87 = 1 << 87;
uint256 internal constant _ROLE_88 = 1 << 88;
uint256 internal constant _ROLE_89 = 1 << 89;
uint256 internal constant _ROLE_90 = 1 << 90;
uint256 internal constant _ROLE_91 = 1 << 91;
uint256 internal constant _ROLE_92 = 1 << 92;
uint256 internal constant _ROLE_93 = 1 << 93;
uint256 internal constant _ROLE_94 = 1 << 94;
uint256 internal constant _ROLE_95 = 1 << 95;
uint256 internal constant _ROLE_96 = 1 << 96;
uint256 internal constant _ROLE_97 = 1 << 97;
uint256 internal constant _ROLE_98 = 1 << 98;
uint256 internal constant _ROLE_99 = 1 << 99;
uint256 internal constant _ROLE_100 = 1 << 100;
uint256 internal constant _ROLE_101 = 1 << 101;
uint256 internal constant _ROLE_102 = 1 << 102;
uint256 internal constant _ROLE_103 = 1 << 103;
uint256 internal constant _ROLE_104 = 1 << 104;
uint256 internal constant _ROLE_105 = 1 << 105;
uint256 internal constant _ROLE_106 = 1 << 106;
uint256 internal constant _ROLE_107 = 1 << 107;
uint256 internal constant _ROLE_108 = 1 << 108;
uint256 internal constant _ROLE_109 = 1 << 109;
uint256 internal constant _ROLE_110 = 1 << 110;
uint256 internal constant _ROLE_111 = 1 << 111;
uint256 internal constant _ROLE_112 = 1 << 112;
uint256 internal constant _ROLE_113 = 1 << 113;
uint256 internal constant _ROLE_114 = 1 << 114;
uint256 internal constant _ROLE_115 = 1 << 115;
uint256 internal constant _ROLE_116 = 1 << 116;
uint256 internal constant _ROLE_117 = 1 << 117;
uint256 internal constant _ROLE_118 = 1 << 118;
uint256 internal constant _ROLE_119 = 1 << 119;
uint256 internal constant _ROLE_120 = 1 << 120;
uint256 internal constant _ROLE_121 = 1 << 121;
uint256 internal constant _ROLE_122 = 1 << 122;
uint256 internal constant _ROLE_123 = 1 << 123;
uint256 internal constant _ROLE_124 = 1 << 124;
uint256 internal constant _ROLE_125 = 1 << 125;
uint256 internal constant _ROLE_126 = 1 << 126;
uint256 internal constant _ROLE_127 = 1 << 127;
uint256 internal constant _ROLE_128 = 1 << 128;
uint256 internal constant _ROLE_129 = 1 << 129;
uint256 internal constant _ROLE_130 = 1 << 130;
uint256 internal constant _ROLE_131 = 1 << 131;
uint256 internal constant _ROLE_132 = 1 << 132;
uint256 internal constant _ROLE_133 = 1 << 133;
uint256 internal constant _ROLE_134 = 1 << 134;
uint256 internal constant _ROLE_135 = 1 << 135;
uint256 internal constant _ROLE_136 = 1 << 136;
uint256 internal constant _ROLE_137 = 1 << 137;
uint256 internal constant _ROLE_138 = 1 << 138;
uint256 internal constant _ROLE_139 = 1 << 139;
uint256 internal constant _ROLE_140 = 1 << 140;
uint256 internal constant _ROLE_141 = 1 << 141;
uint256 internal constant _ROLE_142 = 1 << 142;
uint256 internal constant _ROLE_143 = 1 << 143;
uint256 internal constant _ROLE_144 = 1 << 144;
uint256 internal constant _ROLE_145 = 1 << 145;
uint256 internal constant _ROLE_146 = 1 << 146;
uint256 internal constant _ROLE_147 = 1 << 147;
uint256 internal constant _ROLE_148 = 1 << 148;
uint256 internal constant _ROLE_149 = 1 << 149;
uint256 internal constant _ROLE_150 = 1 << 150;
uint256 internal constant _ROLE_151 = 1 << 151;
uint256 internal constant _ROLE_152 = 1 << 152;
uint256 internal constant _ROLE_153 = 1 << 153;
uint256 internal constant _ROLE_154 = 1 << 154;
uint256 internal constant _ROLE_155 = 1 << 155;
uint256 internal constant _ROLE_156 = 1 << 156;
uint256 internal constant _ROLE_157 = 1 << 157;
uint256 internal constant _ROLE_158 = 1 << 158;
uint256 internal constant _ROLE_159 = 1 << 159;
uint256 internal constant _ROLE_160 = 1 << 160;
uint256 internal constant _ROLE_161 = 1 << 161;
uint256 internal constant _ROLE_162 = 1 << 162;
uint256 internal constant _ROLE_163 = 1 << 163;
uint256 internal constant _ROLE_164 = 1 << 164;
uint256 internal constant _ROLE_165 = 1 << 165;
uint256 internal constant _ROLE_166 = 1 << 166;
uint256 internal constant _ROLE_167 = 1 << 167;
uint256 internal constant _ROLE_168 = 1 << 168;
uint256 internal constant _ROLE_169 = 1 << 169;
uint256 internal constant _ROLE_170 = 1 << 170;
uint256 internal constant _ROLE_171 = 1 << 171;
uint256 internal constant _ROLE_172 = 1 << 172;
uint256 internal constant _ROLE_173 = 1 << 173;
uint256 internal constant _ROLE_174 = 1 << 174;
uint256 internal constant _ROLE_175 = 1 << 175;
uint256 internal constant _ROLE_176 = 1 << 176;
uint256 internal constant _ROLE_177 = 1 << 177;
uint256 internal constant _ROLE_178 = 1 << 178;
uint256 internal constant _ROLE_179 = 1 << 179;
uint256 internal constant _ROLE_180 = 1 << 180;
uint256 internal constant _ROLE_181 = 1 << 181;
uint256 internal constant _ROLE_182 = 1 << 182;
uint256 internal constant _ROLE_183 = 1 << 183;
uint256 internal constant _ROLE_184 = 1 << 184;
uint256 internal constant _ROLE_185 = 1 << 185;
uint256 internal constant _ROLE_186 = 1 << 186;
uint256 internal constant _ROLE_187 = 1 << 187;
uint256 internal constant _ROLE_188 = 1 << 188;
uint256 internal constant _ROLE_189 = 1 << 189;
uint256 internal constant _ROLE_190 = 1 << 190;
uint256 internal constant _ROLE_191 = 1 << 191;
uint256 internal constant _ROLE_192 = 1 << 192;
uint256 internal constant _ROLE_193 = 1 << 193;
uint256 internal constant _ROLE_194 = 1 << 194;
uint256 internal constant _ROLE_195 = 1 << 195;
uint256 internal constant _ROLE_196 = 1 << 196;
uint256 internal constant _ROLE_197 = 1 << 197;
uint256 internal constant _ROLE_198 = 1 << 198;
uint256 internal constant _ROLE_199 = 1 << 199;
uint256 internal constant _ROLE_200 = 1 << 200;
uint256 internal constant _ROLE_201 = 1 << 201;
uint256 internal constant _ROLE_202 = 1 << 202;
uint256 internal constant _ROLE_203 = 1 << 203;
uint256 internal constant _ROLE_204 = 1 << 204;
uint256 internal constant _ROLE_205 = 1 << 205;
uint256 internal constant _ROLE_206 = 1 << 206;
uint256 internal constant _ROLE_207 = 1 << 207;
uint256 internal constant _ROLE_208 = 1 << 208;
uint256 internal constant _ROLE_209 = 1 << 209;
uint256 internal constant _ROLE_210 = 1 << 210;
uint256 internal constant _ROLE_211 = 1 << 211;
uint256 internal constant _ROLE_212 = 1 << 212;
uint256 internal constant _ROLE_213 = 1 << 213;
uint256 internal constant _ROLE_214 = 1 << 214;
uint256 internal constant _ROLE_215 = 1 << 215;
uint256 internal constant _ROLE_216 = 1 << 216;
uint256 internal constant _ROLE_217 = 1 << 217;
uint256 internal constant _ROLE_218 = 1 << 218;
uint256 internal constant _ROLE_219 = 1 << 219;
uint256 internal constant _ROLE_220 = 1 << 220;
uint256 internal constant _ROLE_221 = 1 << 221;
uint256 internal constant _ROLE_222 = 1 << 222;
uint256 internal constant _ROLE_223 = 1 << 223;
uint256 internal constant _ROLE_224 = 1 << 224;
uint256 internal constant _ROLE_225 = 1 << 225;
uint256 internal constant _ROLE_226 = 1 << 226;
uint256 internal constant _ROLE_227 = 1 << 227;
uint256 internal constant _ROLE_228 = 1 << 228;
uint256 internal constant _ROLE_229 = 1 << 229;
uint256 internal constant _ROLE_230 = 1 << 230;
uint256 internal constant _ROLE_231 = 1 << 231;
uint256 internal constant _ROLE_232 = 1 << 232;
uint256 internal constant _ROLE_233 = 1 << 233;
uint256 internal constant _ROLE_234 = 1 << 234;
uint256 internal constant _ROLE_235 = 1 << 235;
uint256 internal constant _ROLE_236 = 1 << 236;
uint256 internal constant _ROLE_237 = 1 << 237;
uint256 internal constant _ROLE_238 = 1 << 238;
uint256 internal constant _ROLE_239 = 1 << 239;
uint256 internal constant _ROLE_240 = 1 << 240;
uint256 internal constant _ROLE_241 = 1 << 241;
uint256 internal constant _ROLE_242 = 1 << 242;
uint256 internal constant _ROLE_243 = 1 << 243;
uint256 internal constant _ROLE_244 = 1 << 244;
uint256 internal constant _ROLE_245 = 1 << 245;
uint256 internal constant _ROLE_246 = 1 << 246;
uint256 internal constant _ROLE_247 = 1 << 247;
uint256 internal constant _ROLE_248 = 1 << 248;
uint256 internal constant _ROLE_249 = 1 << 249;
uint256 internal constant _ROLE_250 = 1 << 250;
uint256 internal constant _ROLE_251 = 1 << 251;
uint256 internal constant _ROLE_252 = 1 << 252;
uint256 internal constant _ROLE_253 = 1 << 253;
uint256 internal constant _ROLE_254 = 1 << 254;
uint256 internal constant _ROLE_255 = 1 << 255;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
pragma solidity ^0.8.20;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback
* function and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.19;
//Efficient Solidity & assembly version of ReentrancyGuard
abstract contract ReentrancyGuard {
error ReentrantCall();
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private _status = 1;
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
assembly {
if eq(sload(_status.slot), 2) {
mstore(0x00, 0x37ed32e8) // ReentrantCall() selector
revert(0x1c, 0x04)
}
sstore(_status.slot, 0x02)
}
_;
assembly {
sstore(_status.slot, 0x01)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe integer casting library that reverts on overflow.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeCastLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
/// @dev Optimized for runtime gas for very high number of optimizer runs (i.e. >= 1000000).
library SafeCastLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error Overflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* UNSIGNED INTEGER SAFE CASTING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function toUint8(uint256 x) internal pure returns (uint8) {
if (x >= 1 << 8) _revertOverflow();
return uint8(x);
}
function toUint16(uint256 x) internal pure returns (uint16) {
if (x >= 1 << 16) _revertOverflow();
return uint16(x);
}
function toUint24(uint256 x) internal pure returns (uint24) {
if (x >= 1 << 24) _revertOverflow();
return uint24(x);
}
function toUint32(uint256 x) internal pure returns (uint32) {
if (x >= 1 << 32) _revertOverflow();
return uint32(x);
}
function toUint40(uint256 x) internal pure returns (uint40) {
if (x >= 1 << 40) _revertOverflow();
return uint40(x);
}
function toUint48(uint256 x) internal pure returns (uint48) {
if (x >= 1 << 48) _revertOverflow();
return uint48(x);
}
function toUint56(uint256 x) internal pure returns (uint56) {
if (x >= 1 << 56) _revertOverflow();
return uint56(x);
}
function toUint64(uint256 x) internal pure returns (uint64) {
if (x >= 1 << 64) _revertOverflow();
return uint64(x);
}
function toUint72(uint256 x) internal pure returns (uint72) {
if (x >= 1 << 72) _revertOverflow();
return uint72(x);
}
function toUint80(uint256 x) internal pure returns (uint80) {
if (x >= 1 << 80) _revertOverflow();
return uint80(x);
}
function toUint88(uint256 x) internal pure returns (uint88) {
if (x >= 1 << 88) _revertOverflow();
return uint88(x);
}
function toUint96(uint256 x) internal pure returns (uint96) {
if (x >= 1 << 96) _revertOverflow();
return uint96(x);
}
function toUint104(uint256 x) internal pure returns (uint104) {
if (x >= 1 << 104) _revertOverflow();
return uint104(x);
}
function toUint112(uint256 x) internal pure returns (uint112) {
if (x >= 1 << 112) _revertOverflow();
return uint112(x);
}
function toUint120(uint256 x) internal pure returns (uint120) {
if (x >= 1 << 120) _revertOverflow();
return uint120(x);
}
function toUint128(uint256 x) internal pure returns (uint128) {
if (x >= 1 << 128) _revertOverflow();
return uint128(x);
}
function toUint136(uint256 x) internal pure returns (uint136) {
if (x >= 1 << 136) _revertOverflow();
return uint136(x);
}
function toUint144(uint256 x) internal pure returns (uint144) {
if (x >= 1 << 144) _revertOverflow();
return uint144(x);
}
function toUint152(uint256 x) internal pure returns (uint152) {
if (x >= 1 << 152) _revertOverflow();
return uint152(x);
}
function toUint160(uint256 x) internal pure returns (uint160) {
if (x >= 1 << 160) _revertOverflow();
return uint160(x);
}
function toUint168(uint256 x) internal pure returns (uint168) {
if (x >= 1 << 168) _revertOverflow();
return uint168(x);
}
function toUint176(uint256 x) internal pure returns (uint176) {
if (x >= 1 << 176) _revertOverflow();
return uint176(x);
}
function toUint184(uint256 x) internal pure returns (uint184) {
if (x >= 1 << 184) _revertOverflow();
return uint184(x);
}
function toUint192(uint256 x) internal pure returns (uint192) {
if (x >= 1 << 192) _revertOverflow();
return uint192(x);
}
function toUint200(uint256 x) internal pure returns (uint200) {
if (x >= 1 << 200) _revertOverflow();
return uint200(x);
}
function toUint208(uint256 x) internal pure returns (uint208) {
if (x >= 1 << 208) _revertOverflow();
return uint208(x);
}
function toUint216(uint256 x) internal pure returns (uint216) {
if (x >= 1 << 216) _revertOverflow();
return uint216(x);
}
function toUint224(uint256 x) internal pure returns (uint224) {
if (x >= 1 << 224) _revertOverflow();
return uint224(x);
}
function toUint232(uint256 x) internal pure returns (uint232) {
if (x >= 1 << 232) _revertOverflow();
return uint232(x);
}
function toUint240(uint256 x) internal pure returns (uint240) {
if (x >= 1 << 240) _revertOverflow();
return uint240(x);
}
function toUint248(uint256 x) internal pure returns (uint248) {
if (x >= 1 << 248) _revertOverflow();
return uint248(x);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNED INTEGER SAFE CASTING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function toInt8(int256 x) internal pure returns (int8) {
unchecked {
if (((1 << 7) + uint256(x)) >> 8 == uint256(0)) return int8(x);
_revertOverflow();
}
}
function toInt16(int256 x) internal pure returns (int16) {
unchecked {
if (((1 << 15) + uint256(x)) >> 16 == uint256(0)) return int16(x);
_revertOverflow();
}
}
function toInt24(int256 x) internal pure returns (int24) {
unchecked {
if (((1 << 23) + uint256(x)) >> 24 == uint256(0)) return int24(x);
_revertOverflow();
}
}
function toInt32(int256 x) internal pure returns (int32) {
unchecked {
if (((1 << 31) + uint256(x)) >> 32 == uint256(0)) return int32(x);
_revertOverflow();
}
}
function toInt40(int256 x) internal pure returns (int40) {
unchecked {
if (((1 << 39) + uint256(x)) >> 40 == uint256(0)) return int40(x);
_revertOverflow();
}
}
function toInt48(int256 x) internal pure returns (int48) {
unchecked {
if (((1 << 47) + uint256(x)) >> 48 == uint256(0)) return int48(x);
_revertOverflow();
}
}
function toInt56(int256 x) internal pure returns (int56) {
unchecked {
if (((1 << 55) + uint256(x)) >> 56 == uint256(0)) return int56(x);
_revertOverflow();
}
}
function toInt64(int256 x) internal pure returns (int64) {
unchecked {
if (((1 << 63) + uint256(x)) >> 64 == uint256(0)) return int64(x);
_revertOverflow();
}
}
function toInt72(int256 x) internal pure returns (int72) {
unchecked {
if (((1 << 71) + uint256(x)) >> 72 == uint256(0)) return int72(x);
_revertOverflow();
}
}
function toInt80(int256 x) internal pure returns (int80) {
unchecked {
if (((1 << 79) + uint256(x)) >> 80 == uint256(0)) return int80(x);
_revertOverflow();
}
}
function toInt88(int256 x) internal pure returns (int88) {
unchecked {
if (((1 << 87) + uint256(x)) >> 88 == uint256(0)) return int88(x);
_revertOverflow();
}
}
function toInt96(int256 x) internal pure returns (int96) {
unchecked {
if (((1 << 95) + uint256(x)) >> 96 == uint256(0)) return int96(x);
_revertOverflow();
}
}
function toInt104(int256 x) internal pure returns (int104) {
unchecked {
if (((1 << 103) + uint256(x)) >> 104 == uint256(0)) return int104(x);
_revertOverflow();
}
}
function toInt112(int256 x) internal pure returns (int112) {
unchecked {
if (((1 << 111) + uint256(x)) >> 112 == uint256(0)) return int112(x);
_revertOverflow();
}
}
function toInt120(int256 x) internal pure returns (int120) {
unchecked {
if (((1 << 119) + uint256(x)) >> 120 == uint256(0)) return int120(x);
_revertOverflow();
}
}
function toInt128(int256 x) internal pure returns (int128) {
unchecked {
if (((1 << 127) + uint256(x)) >> 128 == uint256(0)) return int128(x);
_revertOverflow();
}
}
function toInt136(int256 x) internal pure returns (int136) {
unchecked {
if (((1 << 135) + uint256(x)) >> 136 == uint256(0)) return int136(x);
_revertOverflow();
}
}
function toInt144(int256 x) internal pure returns (int144) {
unchecked {
if (((1 << 143) + uint256(x)) >> 144 == uint256(0)) return int144(x);
_revertOverflow();
}
}
function toInt152(int256 x) internal pure returns (int152) {
unchecked {
if (((1 << 151) + uint256(x)) >> 152 == uint256(0)) return int152(x);
_revertOverflow();
}
}
function toInt160(int256 x) internal pure returns (int160) {
unchecked {
if (((1 << 159) + uint256(x)) >> 160 == uint256(0)) return int160(x);
_revertOverflow();
}
}
function toInt168(int256 x) internal pure returns (int168) {
unchecked {
if (((1 << 167) + uint256(x)) >> 168 == uint256(0)) return int168(x);
_revertOverflow();
}
}
function toInt176(int256 x) internal pure returns (int176) {
unchecked {
if (((1 << 175) + uint256(x)) >> 176 == uint256(0)) return int176(x);
_revertOverflow();
}
}
function toInt184(int256 x) internal pure returns (int184) {
unchecked {
if (((1 << 183) + uint256(x)) >> 184 == uint256(0)) return int184(x);
_revertOverflow();
}
}
function toInt192(int256 x) internal pure returns (int192) {
unchecked {
if (((1 << 191) + uint256(x)) >> 192 == uint256(0)) return int192(x);
_revertOverflow();
}
}
function toInt200(int256 x) internal pure returns (int200) {
unchecked {
if (((1 << 199) + uint256(x)) >> 200 == uint256(0)) return int200(x);
_revertOverflow();
}
}
function toInt208(int256 x) internal pure returns (int208) {
unchecked {
if (((1 << 207) + uint256(x)) >> 208 == uint256(0)) return int208(x);
_revertOverflow();
}
}
function toInt216(int256 x) internal pure returns (int216) {
unchecked {
if (((1 << 215) + uint256(x)) >> 216 == uint256(0)) return int216(x);
_revertOverflow();
}
}
function toInt224(int256 x) internal pure returns (int224) {
unchecked {
if (((1 << 223) + uint256(x)) >> 224 == uint256(0)) return int224(x);
_revertOverflow();
}
}
function toInt232(int256 x) internal pure returns (int232) {
unchecked {
if (((1 << 231) + uint256(x)) >> 232 == uint256(0)) return int232(x);
_revertOverflow();
}
}
function toInt240(int256 x) internal pure returns (int240) {
unchecked {
if (((1 << 239) + uint256(x)) >> 240 == uint256(0)) return int240(x);
_revertOverflow();
}
}
function toInt248(int256 x) internal pure returns (int248) {
unchecked {
if (((1 << 247) + uint256(x)) >> 248 == uint256(0)) return int248(x);
_revertOverflow();
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OTHER SAFE CASTING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function toInt8(uint256 x) internal pure returns (int8) {
if (x >= 1 << 7) _revertOverflow();
return int8(int256(x));
}
function toInt16(uint256 x) internal pure returns (int16) {
if (x >= 1 << 15) _revertOverflow();
return int16(int256(x));
}
function toInt24(uint256 x) internal pure returns (int24) {
if (x >= 1 << 23) _revertOverflow();
return int24(int256(x));
}
function toInt32(uint256 x) internal pure returns (int32) {
if (x >= 1 << 31) _revertOverflow();
return int32(int256(x));
}
function toInt40(uint256 x) internal pure returns (int40) {
if (x >= 1 << 39) _revertOverflow();
return int40(int256(x));
}
function toInt48(uint256 x) internal pure returns (int48) {
if (x >= 1 << 47) _revertOverflow();
return int48(int256(x));
}
function toInt56(uint256 x) internal pure returns (int56) {
if (x >= 1 << 55) _revertOverflow();
return int56(int256(x));
}
function toInt64(uint256 x) internal pure returns (int64) {
if (x >= 1 << 63) _revertOverflow();
return int64(int256(x));
}
function toInt72(uint256 x) internal pure returns (int72) {
if (x >= 1 << 71) _revertOverflow();
return int72(int256(x));
}
function toInt80(uint256 x) internal pure returns (int80) {
if (x >= 1 << 79) _revertOverflow();
return int80(int256(x));
}
function toInt88(uint256 x) internal pure returns (int88) {
if (x >= 1 << 87) _revertOverflow();
return int88(int256(x));
}
function toInt96(uint256 x) internal pure returns (int96) {
if (x >= 1 << 95) _revertOverflow();
return int96(int256(x));
}
function toInt104(uint256 x) internal pure returns (int104) {
if (x >= 1 << 103) _revertOverflow();
return int104(int256(x));
}
function toInt112(uint256 x) internal pure returns (int112) {
if (x >= 1 << 111) _revertOverflow();
return int112(int256(x));
}
function toInt120(uint256 x) internal pure returns (int120) {
if (x >= 1 << 119) _revertOverflow();
return int120(int256(x));
}
function toInt128(uint256 x) internal pure returns (int128) {
if (x >= 1 << 127) _revertOverflow();
return int128(int256(x));
}
function toInt136(uint256 x) internal pure returns (int136) {
if (x >= 1 << 135) _revertOverflow();
return int136(int256(x));
}
function toInt144(uint256 x) internal pure returns (int144) {
if (x >= 1 << 143) _revertOverflow();
return int144(int256(x));
}
function toInt152(uint256 x) internal pure returns (int152) {
if (x >= 1 << 151) _revertOverflow();
return int152(int256(x));
}
function toInt160(uint256 x) internal pure returns (int160) {
if (x >= 1 << 159) _revertOverflow();
return int160(int256(x));
}
function toInt168(uint256 x) internal pure returns (int168) {
if (x >= 1 << 167) _revertOverflow();
return int168(int256(x));
}
function toInt176(uint256 x) internal pure returns (int176) {
if (x >= 1 << 175) _revertOverflow();
return int176(int256(x));
}
function toInt184(uint256 x) internal pure returns (int184) {
if (x >= 1 << 183) _revertOverflow();
return int184(int256(x));
}
function toInt192(uint256 x) internal pure returns (int192) {
if (x >= 1 << 191) _revertOverflow();
return int192(int256(x));
}
function toInt200(uint256 x) internal pure returns (int200) {
if (x >= 1 << 199) _revertOverflow();
return int200(int256(x));
}
function toInt208(uint256 x) internal pure returns (int208) {
if (x >= 1 << 207) _revertOverflow();
return int208(int256(x));
}
function toInt216(uint256 x) internal pure returns (int216) {
if (x >= 1 << 215) _revertOverflow();
return int216(int256(x));
}
function toInt224(uint256 x) internal pure returns (int224) {
if (x >= 1 << 223) _revertOverflow();
return int224(int256(x));
}
function toInt232(uint256 x) internal pure returns (int232) {
if (x >= 1 << 231) _revertOverflow();
return int232(int256(x));
}
function toInt240(uint256 x) internal pure returns (int240) {
if (x >= 1 << 239) _revertOverflow();
return int240(int256(x));
}
function toInt248(uint256 x) internal pure returns (int248) {
if (x >= 1 << 247) _revertOverflow();
return int248(int256(x));
}
function toInt256(uint256 x) internal pure returns (int256) {
if (int256(x) >= 0) return int256(x);
_revertOverflow();
}
function toUint256(int256 x) internal pure returns (uint256) {
if (x >= 0) return uint256(x);
_revertOverflow();
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function _revertOverflow() private pure {
/// @solidity memory-safe-assembly
assembly {
// Store the function selector of `Overflow()`.
mstore(0x00, 0x35278d12)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success :=
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `1` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
/// to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNATURE CHECKING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
if eq(mload(signature), 64) {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), mload(0x60)) // `s`.
mstore8(add(m, 0xa4), mload(0x20)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, s) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: These ERC1271 operations do NOT have an ECDSA fallback.
// These functions are intended to be used with the regular `isValidSignatureNow` functions
// or other signature verification functions (e.g. P256).
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC6492 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// Note: These ERC6492 operations do NOT have an ECDSA fallback.
// These functions are intended to be used with the regular `isValidSignatureNow` functions
// or other signature verification functions (e.g. P256).
// The calldata variants are excluded for brevity.
/// @dev Returns whether `signature` is valid for `hash`.
/// If the signature is postfixed with the ERC6492 magic number, it will attempt to
/// deploy / prepare the `signer` smart account before doing a regular ERC1271 check.
/// Note: This function is NOT reentrancy safe.
function isValidERC6492SignatureNowAllowSideEffects(
address signer,
bytes32 hash,
bytes memory signature
) internal returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
let m_ := mload(0x40)
let f_ := shl(224, 0x1626ba7e)
mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m_, 0x04), hash_)
let d_ := add(m_, 0x24)
mstore(d_, 0x40) // The offset of the `signature` in the calldata.
let n_ := add(0x20, mload(signature_))
pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
_isValid :=
and(
eq(mload(d_), f_),
staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
)
}
for { let n := mload(signature) } 1 {} {
if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
isValid := callIsValidSignature(signer, hash, signature)
break
}
let o := add(signature, 0x20) // Signature bytes.
let d := add(o, mload(add(o, 0x20))) // Factory calldata.
if iszero(extcodesize(signer)) {
if iszero(call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00)) {
break
}
}
let s := add(o, mload(add(o, 0x40))) // Inner signature.
isValid := callIsValidSignature(signer, hash, s)
if iszero(isValid) {
if call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00) {
isValid := callIsValidSignature(signer, hash, s)
}
}
break
}
}
}
/// @dev Returns whether `signature` is valid for `hash`.
/// If the signature is postfixed with the ERC6492 magic number, it will attempt
/// to use a reverting verifier to deploy / prepare the `signer` smart account
/// and do a `isValidSignature` check via the reverting verifier.
/// Note: This function is reentrancy safe.
/// The reverting verifier must be deployed.
/// Otherwise, the function will return false if `signer` is not yet deployed / prepared.
/// See: https://gist.github.com/Vectorized/846a474c855eee9e441506676800a9ad
function isValidERC6492SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
let m_ := mload(0x40)
let f_ := shl(224, 0x1626ba7e)
mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m_, 0x04), hash_)
let d_ := add(m_, 0x24)
mstore(d_, 0x40) // The offset of the `signature` in the calldata.
let n_ := add(0x20, mload(signature_))
pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
_isValid :=
and(
eq(mload(d_), f_),
staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
)
}
for { let n := mload(signature) } 1 {} {
if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
isValid := callIsValidSignature(signer, hash, signature)
break
}
if extcodesize(signer) {
let o := add(signature, 0x20) // Signature bytes.
isValid := callIsValidSignature(signer, hash, add(o, mload(add(o, 0x40))))
if isValid { break }
}
let m := mload(0x40)
mstore(m, signer)
mstore(add(m, 0x20), hash)
let willBeZeroIfRevertingVerifierExists :=
call(
gas(), // Remaining gas.
0x00007bd799e4A591FeA53f8A8a3E9f931626Ba7e, // Reverting verifier.
0, // Send zero ETH.
m, // Start of memory.
add(returndatasize(), 0x40), // Length of calldata in memory.
staticcall(gas(), 4, add(signature, 0x20), n, add(m, 0x40), n), // 1.
0x00 // Length of returndata to write.
)
isValid := gt(returndatasize(), willBeZeroIfRevertingVerifierExists)
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}
/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @dev contains all the common struct and enums used for data communication between chains.
/// @dev There are two transaction types in Superform Protocol
enum TransactionType {
DEPOSIT,
WITHDRAW
}
/// @dev Message types can be INIT, RETURN (for successful Deposits) and FAIL (for failed withdraws)
enum CallbackType {
INIT,
RETURN,
FAIL
}
/// @dev Payloads are stored, updated (deposits) or processed (finalized)
enum PayloadState {
STORED,
UPDATED,
PROCESSED
}
/// @dev contains all the common struct used for interchain token transfers.
struct LiqRequest {
/// @dev generated data
bytes txData;
/// @dev input token for deposits, desired output token on target liqDstChainId for withdraws. Must be set for
/// txData to be updated on destination for withdraws
address token;
/// @dev intermediary token on destination. Relevant for xChain deposits where a destination swap is needed for
/// validation purposes
address interimToken;
/// @dev what bridge to use to move tokens
uint8 bridgeId;
/// @dev dstChainId = liqDstchainId for deposits. For withdraws it is the target chain id for where the underlying
/// is to be delivered
uint64 liqDstChainId;
/// @dev currently this amount is used as msg.value in the txData call.
uint256 nativeAmount;
}
/// @dev main struct that holds required multi vault data for an action
struct MultiVaultSFData {
// superformids must have same destination. Can have different underlyings
uint256[] superformIds;
uint256[] amounts; // on deposits, amount of token to deposit on dst, on withdrawals, superpositions to burn
uint256[] outputAmounts; // on deposits, amount of shares to receive, on withdrawals, amount of assets to receive
uint256[] maxSlippages;
LiqRequest[] liqRequests; // if length = 1; amount = sum(amounts) | else amounts must match the amounts being sent
bytes permit2data;
bool[] hasDstSwaps;
bool[] retain4626s; // if true, we don't mint SuperPositions, and send the 4626 back to the user instead
address receiverAddress;
/// this address must always be an EOA otherwise funds may be lost
address receiverAddressSP;
/// this address can be a EOA or a contract that implements onERC1155Receiver. must always be set for deposits
bytes extraFormData; // extraFormData
}
/// @dev main struct that holds required single vault data for an action
struct SingleVaultSFData {
// superformids must have same destination. Can have different underlyings
uint256 superformId;
uint256 amount;
uint256 outputAmount; // on deposits, amount of shares to receive, on withdrawals, amount of assets to receive
uint256 maxSlippage;
LiqRequest liqRequest; // if length = 1; amount = sum(amounts)| else amounts must match the amounts being sent
bytes permit2data;
bool hasDstSwap;
bool retain4626; // if true, we don't mint SuperPositions, and send the 4626 back to the user instead
address receiverAddress;
/// this address must always be an EOA otherwise funds may be lost
address receiverAddressSP;
/// this address can be a EOA or a contract that implements onERC1155Receiver. must always be set for deposits
bytes extraFormData; // extraFormData
}
/// @dev overarching struct for multiDst requests with multi vaults
struct MultiDstMultiVaultStateReq {
uint8[][] ambIds;
uint64[] dstChainIds;
MultiVaultSFData[] superformsData;
}
/// @dev overarching struct for single cross chain requests with multi vaults
struct SingleXChainMultiVaultStateReq {
uint8[] ambIds;
uint64 dstChainId;
MultiVaultSFData superformsData;
}
/// @dev overarching struct for multiDst requests with single vaults
struct MultiDstSingleVaultStateReq {
uint8[][] ambIds;
uint64[] dstChainIds;
SingleVaultSFData[] superformsData;
}
/// @dev overarching struct for single cross chain requests with single vaults
struct SingleXChainSingleVaultStateReq {
uint8[] ambIds;
uint64 dstChainId;
SingleVaultSFData superformData;
}
/// @dev overarching struct for single direct chain requests with single vaults
struct SingleDirectSingleVaultStateReq {
SingleVaultSFData superformData;
}
/// @dev overarching struct for single direct chain requests with multi vaults
struct SingleDirectMultiVaultStateReq {
MultiVaultSFData superformData;
}
/// @dev struct for SuperRouter with re-arranged data for the message (contains the payloadId)
/// @dev realize that receiverAddressSP is not passed, only needed on source chain to mint
struct InitMultiVaultData {
uint256 payloadId;
uint256[] superformIds;
uint256[] amounts;
uint256[] outputAmounts;
uint256[] maxSlippages;
LiqRequest[] liqData;
bool[] hasDstSwaps;
bool[] retain4626s;
address receiverAddress;
bytes extraFormData;
}
/// @dev struct for SuperRouter with re-arranged data for the message (contains the payloadId)
struct InitSingleVaultData {
uint256 payloadId;
uint256 superformId;
uint256 amount;
uint256 outputAmount;
uint256 maxSlippage;
LiqRequest liqData;
bool hasDstSwap;
bool retain4626;
address receiverAddress;
bytes extraFormData;
}
/// @dev struct for Emergency Queue
struct QueuedWithdrawal {
address receiverAddress;
uint256 superformId;
uint256 amount;
uint256 srcPayloadId;
bool isProcessed;
}
/// @dev all statuses of the timelock payload
enum TimelockStatus {
UNAVAILABLE,
PENDING,
PROCESSED
}
/// @dev holds information about the timelock payload
struct TimelockPayload {
uint8 isXChain;
uint64 srcChainId;
uint256 lockedTill;
InitSingleVaultData data;
TimelockStatus status;
}
/// @dev struct that contains the type of transaction, callback flags and other identification, as well as the vaults
/// data in params
struct AMBMessage {
uint256 txInfo; // tight packing of TransactionType txType, CallbackType flag if multi/single vault, registry id,
// srcSender and srcChainId
bytes params; // decoding txInfo will point to the right datatype of params. Refer PayloadHelper.sol
}
/// @dev struct that contains the information required for broadcasting changes
struct BroadcastMessage {
bytes target;
bytes32 messageType;
bytes message;
}
/// @dev struct that contains info on returned data from destination
struct ReturnMultiData {
uint256 payloadId;
uint256[] superformIds;
uint256[] amounts;
}
/// @dev struct that contains info on returned data from destination
struct ReturnSingleData {
uint256 payloadId;
uint256 superformId;
uint256 amount;
}
/// @dev struct that contains the data on the fees to pay to the AMBs
struct AMBExtraData {
uint256[] gasPerAMB;
bytes[] extraDataPerAMB;
}
/// @dev struct that contains the data on the fees to pay to the AMBs on broadcasts
struct BroadCastAMBExtraData {
uint256[] gasPerDst;
bytes[] extraDataPerDst;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import { LiqRequest } from "./SuperformTypes.sol";
import { ISharePriceOracle, VaultReport } from "interfaces/ISharePriceOracle.sol";
import { ERC4626 } from "solady/tokens/ERC4626.sol";
import { IBaseRouter } from "src/interfaces/IBaseRouter.sol";
import { IHurdleRateOracle } from "src/interfaces/IHurdleRateOracle.sol";
import { ISuperPositions } from "src/interfaces/ISuperPositions.sol";
import { ISuperformFactory } from "src/interfaces/ISuperformFactory.sol";
import { ISuperformGateway } from "src/interfaces/ISuperformGateway.sol";
/// @dev The maximum allowable staleness for oracle data before being considered outdated
uint256 constant ORACLE_STALENESS_TOLERANCE = 1 days;
/// @notice A struct describing the status of an underlying vault
/// @dev Contains data about a vault's chain ID, share price, oracle, and more
struct VaultData {
/// @dev The ID of the chain where the vault is deployed
uint32 chainId;
/// @dev The superform ID of the vault in the Superform protocol
uint256 superformId;
/// @dev The oracle that provides the share price for the vault
ISharePriceOracle oracle;
/// @dev The number of decimals used in the ERC4626 shares
uint8 decimals;
/// @dev The total assets invested in the vault
uint128 totalDebt;
/// @dev The address of the vault
address vaultAddress;
}
/// @notice Configuration parameters for a vault
/// @dev Contains all the necessary settings and addresses for vault operation
struct VaultConfig {
/// @dev The address of the underlying asset token
address asset;
/// @dev The name of the vault
string name;
/// @dev The symbol of the vault
string symbol;
/// @dev The management fee in basis points
uint16 managementFee;
/// @dev The performance fee in basis points
uint16 performanceFee;
/// @dev The oracle fee in basis points
uint16 oracleFee;
/// @dev The lock time for shares in seconds
uint24 sharesLockTime;
/// @dev The oracle contract for hurdle rates
IHurdleRateOracle hurdleRateOracle;
/// @dev The SuperPositions contract
ISuperPositions superPositions;
/// @dev The treasury address for fee collection
address treasury;
/// @dev The address of the signer relayer
address signerRelayer;
/// @dev The owner address of the vault
address owner;
}
/// @notice A helper library to define methods for handling VaultData
/// @dev Provides methods to simulate conversions between shares and assets for a vault
library VaultLib {
error StaleSharePrice();
/// @notice Simulates the ERC4626 {convertToAssets} function using vault data
/// @param self The vault data to operate on
/// @param shares The number of shares to convert to assets
/// @param metavaultAsset The address of the metavault asset
/// @param revertIfStale Whether to revert the transaction if the oracle data is stale
/// @return assets The equivalent amount of assets for the given shares
function convertToAssets(
VaultData memory self,
uint256 shares,
address metavaultAsset,
bool revertIfStale
)
internal
view
returns (uint256 assets)
{
(uint256 sharePrice_, uint64 lastUpdated) =
self.oracle.getLatestSharePrice(self.chainId, self.vaultAddress, metavaultAsset);
if (revertIfStale) {
if (lastUpdated + ORACLE_STALENESS_TOLERANCE < block.timestamp) revert StaleSharePrice();
}
return sharePrice_ * shares / 10 ** self.decimals;
}
/// @notice Simulates the ERC4626 {convertToShares} function using vault data
/// @param self The vault data to operate on
/// @param assets The number of assets to convert to shares
/// @param metavaultAsset The address of the metavault asset
/// @param revertIfStale Whether to revert the transaction if the oracle data is stale
/// @return shares The equivalent amount of shares for the given assets
function convertToShares(
VaultData memory self,
uint256 assets,
address metavaultAsset,
bool revertIfStale
)
internal
view
returns (uint256 shares)
{
(uint256 sharePrice_, uint64 lastUpdated) =
self.oracle.getLatestSharePrice(self.chainId, self.vaultAddress, metavaultAsset);
if (revertIfStale) {
if (lastUpdated + ORACLE_STALENESS_TOLERANCE < block.timestamp) revert();
}
return assets * 10 ** self.decimals / sharePrice_;
}
/// @notice Retrieves the current share price of the vault
/// @param self The vault data to operate on
/// @param metavaultAsset The address of the metavault asset
/// @return The current share price
function sharePrice(VaultData memory self, address metavaultAsset) internal view returns (uint256) {
(uint256 sharePrice_,) = self.oracle.getLatestSharePrice(self.chainId, self.vaultAddress, metavaultAsset);
return sharePrice_;
}
/// @notice Retrieves the current chain ID
/// @return chainId The current chain ID
function _chainId() internal view returns (uint64 chainId) {
return uint64(block.chainid);
}
}
/// @notice Parameters for single cross-chain single vault withdrawal
/// @dev Contains data needed for withdrawing from one vault on a different chain
struct SingleXChainSingleVaultWithdraw {
/// @dev Array of AMB (arbitrary message bridge) IDs to use
uint8[] ambIds;
/// @dev Expected output amount from the withdrawal
uint256 outputAmount;
/// @dev Maximum acceptable slippage for the withdrawal
uint256 maxSlippage;
/// @dev Liquidity request parameters
LiqRequest liqRequest;
/// @dev Flag indicating if there's a swap on the destination chain
bool hasDstSwap;
/// @dev Native token value to be sent with the transaction
uint256 value;
}
/// @notice Parameters for single cross-chain multi vault withdrawal
/// @dev Contains data needed for withdrawing from multiple vaults on a different chain
struct SingleXChainMultiVaultWithdraw {
/// @dev Array of AMB IDs to use
uint8[] ambIds;
/// @dev Array of expected output amounts for each vault
uint256[] outputAmounts;
/// @dev Array of maximum acceptable slippages for each vault
uint256[] maxSlippages;
/// @dev Array of liquidity request parameters for each vault
LiqRequest[] liqRequests;
/// @dev Array of flags indicating if there are swaps on the destination chain
bool[] hasDstSwaps;
/// @dev Native token value to be sent with the transaction
uint256 value;
}
/// @notice Parameters for multi cross-chain single vault withdrawal
/// @dev Contains data needed for withdrawing from one vault across multiple chains
struct MultiXChainSingleVaultWithdraw {
/// @dev 2D array of AMB IDs to use for each chain
uint8[][] ambIds;
/// @dev Array of expected output amounts for each chain
uint256[] outputAmounts;
/// @dev Array of maximum acceptable slippages for each chain
uint256[] maxSlippages;
/// @dev Array of liquidity request parameters for each chain
LiqRequest[] liqRequests;
/// @dev Array of flags indicating if there are swaps on destination chains
bool[] hasDstSwaps;
/// @dev Native token value to be sent with the transaction
uint256 value;
}
/// @notice Parameters for multi cross-chain multi vault withdrawal
/// @dev Contains data needed for withdrawing from multiple vaults across multiple chains
struct MultiXChainMultiVaultWithdraw {
/// @dev 2D array of AMB IDs to use for each chain
uint8[][] ambIds;
/// @dev 2D array of expected output amounts for each vault on each chain
uint256[][] outputAmounts;
/// @dev 2D array of maximum acceptable slippages for each vault on each chain
uint256[][] maxSlippages;
/// @dev 2D array of liquidity request parameters for each vault on each chain
LiqRequest[][] liqRequests;
/// @dev 2D array of flags indicating if there are swaps on destination chains
bool[][] hasDstSwaps;
/// @dev Native token value to be sent with the transaction
uint256 value;
}
/// @notice Parameters for processing a redeem request
/// @dev Contains all necessary data to process a withdrawal request
struct ProcessRedeemRequestParams {
/// @dev Address of the controller initiating the redemption
address controller;
/// @dev Number of shares to redeem
uint256 shares;
/// @dev Single cross-chain single vault withdrawal parameters
SingleXChainSingleVaultWithdraw sXsV;
/// @dev Single cross-chain multi vault withdrawal parameters
SingleXChainMultiVaultWithdraw sXmV;
/// @dev Multi cross-chain single vault withdrawal parameters
MultiXChainSingleVaultWithdraw mXsV;
/// @dev Multi cross-chain multi vault withdrawal parameters
MultiXChainMultiVaultWithdraw mXmV;
}
{
"compilationTarget": {
"src/MetaVault.sol": "MetaVault"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin-contracts-5.1.0/=dependencies/@openzeppelin-contracts-5.1.0/",
":common/=src/common/",
":crosschain/=src/crosschain/",
":forge-std-1.9.2/=dependencies/forge-std-1.9.2/src/",
":forge-std/=dependencies/forge-std-1.9.2/src/",
":helpers/=src/helpers/",
":interfaces/=src/interfaces/",
":lib/=src/lib/",
":modules/=src/modules/",
":openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.1.0/",
":solady-0.0.236/=dependencies/solady-0.0.236/src/",
":solady/=dependencies/solady-0.0.236/src/",
":types/=src/types/"
],
"viaIR": true
}
[{"inputs":[{"components":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint16","name":"managementFee","type":"uint16"},{"internalType":"uint16","name":"performanceFee","type":"uint16"},{"internalType":"uint16","name":"oracleFee","type":"uint16"},{"internalType":"uint24","name":"sharesLockTime","type":"uint24"},{"internalType":"contract IHurdleRateOracle","name":"hurdleRateOracle","type":"address"},{"internalType":"contract ISuperPositions","name":"superPositions","type":"address"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"address","name":"signerRelayer","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"internalType":"struct VaultConfig","name":"config","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AllowanceOverflow","type":"error"},{"inputs":[],"name":"AllowanceUnderflow","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"DelegateCallNotAllowed","type":"error"},{"inputs":[],"name":"DepositMoreThanMax","type":"error"},{"inputs":[],"name":"DuplicateVaultInOrder","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientAvailableAssets","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidController","type":"error"},{"inputs":[],"name":"InvalidOperator","type":"error"},{"inputs":[],"name":"InvalidPermit","type":"error"},{"inputs":[],"name":"InvalidQueueType","type":"error"},{"inputs":[],"name":"InvalidSuperformId","type":"error"},{"inputs":[],"name":"InvalidVaultAddress","type":"error"},{"inputs":[],"name":"InvalidZeroAddress","type":"error"},{"inputs":[],"name":"InvalidZeroAssets","type":"error"},{"inputs":[],"name":"InvalidZeroShares","type":"error"},{"inputs":[],"name":"MaxQueueSizeExceeded","type":"error"},{"inputs":[],"name":"MintMoreThanMax","type":"error"},{"inputs":[],"name":"MissingVaultFromCurrentQueue","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NewVaultNotInCurrentQueue","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"PermitExpired","type":"error"},{"inputs":[],"name":"RedeemMoreThanMax","type":"error"},{"inputs":[],"name":"RedeemNotProcessed","type":"error"},{"inputs":[],"name":"ReentrantCall","type":"error"},{"inputs":[],"name":"RequestNotSettled","type":"error"},{"inputs":[],"name":"SharesBalanceNotZero","type":"error"},{"inputs":[],"name":"SharesLocked","type":"error"},{"inputs":[],"name":"StaleSharePrice","type":"error"},{"inputs":[],"name":"TotalSupplyOverflow","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"VaultAlreadyListed","type":"error"},{"inputs":[],"name":"VaultCountMismatch","type":"error"},{"inputs":[],"name":"VaultShutdown","type":"error"},{"inputs":[],"name":"WithdrawMoreThanMax","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"chainId","type":"uint64"},{"indexed":false,"internalType":"address","name":"vault","type":"address"}],"name":"AddVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":false,"internalType":"uint256","name":"managementFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"performanceFees","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oracleFees","type":"uint256"}],"name":"AssessFees","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"address","name":"source","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"DepositRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"EmergencyShutdown","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"FulfillDepositRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"FulfillRedeemRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"OperatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"address","name":"source","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"RedeemRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"chainId","type":"uint64"},{"indexed":false,"internalType":"address","name":"vault","type":"address"}],"name":"RemoveVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"roles","type":"uint256"}],"name":"RolesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"fee","type":"uint16"}],"name":"SetManagementFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"fee","type":"uint16"}],"name":"SetOracleFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"fee","type":"uint16"}],"name":"SetPerformanceFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"time","type":"uint24"}],"name":"SetSharesLockTime","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"treasury","type":"address"}],"name":"TreasuryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"ADMIN_ROLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"result","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"DST_CHAINS","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EMERGENCY_ADMIN_ROLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGER_ROLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"N_CHAINS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ORACLE_ROLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAYER_ROLE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECS_PER_YEAR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"THIS_CHAIN_ID","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAWAL_QUEUE_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bool","name":"forceOverride","type":"bool"}],"name":"addFunction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4[]","name":"selectors","type":"bytes4[]"},{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bool","name":"forceOverride","type":"bool"}],"name":"addFunctions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"chainId","type":"uint32"},{"internalType":"uint256","name":"superformId","type":"uint256"},{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint8","name":"vaultDecimals","type":"uint8"},{"internalType":"contract ISharePriceOracle","name":"oracle","type":"address"}],"name":"addVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"chargeGlobalFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"claimableDepositRequest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"claimableRedeemRequest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"superformId","type":"uint256"},{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToSuperPositions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"controller","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"donate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dustThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyShutdown","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gateway","outputs":[{"internalType":"contract ISuperformGateway","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"superformId","type":"uint256"}],"name":"getVault","outputs":[{"components":[{"internalType":"uint32","name":"chainId","type":"uint32"},{"internalType":"uint256","name":"superformId","type":"uint256"},{"internalType":"contract ISharePriceOracle","name":"oracle","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint128","name":"totalDebt","type":"uint128"},{"internalType":"address","name":"vaultAddress","type":"address"}],"internalType":"struct VaultData","name":"vault","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"grantRoles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"hasAllRoles","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"hasAnyRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hurdleRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vaultAddress","type":"address"}],"name":"isVaultListed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"superformId","type":"uint256"}],"name":"isVaultListed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastFeesCharged","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"lastRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastReport","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"localWithdrawalQueue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"managementFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"managementFeeExempt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"controller","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256[]","name":"superformIds","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"superformId","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"oracleFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"oracleFeeExempt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"pendingDepositRequest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"pendingProcessedShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"pendingRedeemRequest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"performanceFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"performanceFeeExempt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"positions","outputs":[{"internalType":"uint256","name":"averageEntryPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint8","name":"queueType","type":"uint8"},{"internalType":"uint256[30]","name":"newOrder","type":"uint256[30]"}],"name":"rearrangeWithdrawalQueue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"controller","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"removeFunction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4[]","name":"selectors","type":"bytes4[]"}],"name":"removeFunctions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"superformId","type":"uint256"}],"name":"removeVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"renounceRoles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"controller","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"requestDeposit","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"controller","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"requestRedeem","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"roles","type":"uint256"}],"name":"revokeRoles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"rolesOf","outputs":[{"internalType":"uint256","name":"roles","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setOperator","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sharePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sharePriceWaterMark","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sharesLockTime","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"signerRelayer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"isSupported","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDebt","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDeposits","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalIdle","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalLocalAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWithdrawableAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalXChainAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"vaults","outputs":[{"internalType":"uint32","name":"chainId","type":"uint32"},{"internalType":"uint256","name":"superformId","type":"uint256"},{"internalType":"contract ISharePriceOracle","name":"oracle","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint128","name":"totalDebt","type":"uint128"},{"internalType":"address","name":"vaultAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"controller","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"xChainWithdrawalQueue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]