Vault
Inherits: ERC4626, Multicall, AccessControlDefaultAdminRules, ReentrancyGuard
Author: Molecular Labs
Vault contract that can allocate a single lender asset over various isolated lending pairs on Ion Protocol. This contract is a fork of the Metamorpho contract licnesed under GPL-2.0 with changes to administrative logic, underlying data structures, and lending interactions to be made compatible with Ion Protocol.
State Variables
OWNER_ROLE
bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");
ALLOCATOR_ROLE
bytes32 public constant ALLOCATOR_ROLE = keccak256("ALLOCATOR_ROLE");
IDLE
IIonPool public constant IDLE = IIonPool(address(uint160(uint256(keccak256("IDLE_ASSET_HOLDINGS")))));
DECIMALS_OFFSET
uint8 public immutable DECIMALS_OFFSET;
ION_POOL_SUPPLY_CAP_SLOT
bytes32 public immutable ION_POOL_SUPPLY_CAP_SLOT = 0xceba3d526b4d5afd91d1b752bf1fd37917c20a6daf576bcb41dd1c57c1f67e09;
ION_POOL_LIQUIDITY_SLOT
bytes32 public immutable ION_POOL_LIQUIDITY_SLOT = 0xceba3d526b4d5afd91d1b752bf1fd37917c20a6daf576bcb41dd1c57c1f67e08;
BASE_ASSET
IERC20 public immutable BASE_ASSET;
MAX_SUPPORTED_MARKETS
uint8 public constant MAX_SUPPORTED_MARKETS = 32;
supportedMarkets
EnumerableSet.AddressSet supportedMarkets;
supplyQueue
IIonPool[] public supplyQueue;
withdrawQueue
IIonPool[] public withdrawQueue;
feeRecipient
address public feeRecipient;
feePercentage
uint256 public feePercentage;
lastTotalAssets
uint256 public lastTotalAssets;
caps
mapping(IIonPool => uint256) public caps;
Functions
constructor
constructor(
IERC20 _baseAsset,
address _feeRecipient,
uint256 _feePercentage,
string memory _name,
string memory _symbol,
uint48 initialDelay,
address initialDefaultAdmin,
MarketsArgs memory marketsArgs
)
ERC4626(_baseAsset)
ERC20(_name, _symbol)
AccessControlDefaultAdminRules(initialDelay, initialDefaultAdmin);
updateFeePercentage
Updates the fee percentage.
Input must be in [RAY]. Ex) 2% would be 0.02e27.
function updateFeePercentage(uint256 _feePercentage) external onlyRole(OWNER_ROLE);
Parameters
_feePercentage
uint256
The percentage of the interest accrued to take as a management fee.
updateFeeRecipient
Updates the fee recipient.
function updateFeeRecipient(address _feeRecipient) external onlyRole(OWNER_ROLE);
Parameters
_feeRecipient
address
The recipient address of the shares minted as fees.
addSupportedMarkets
Add markets that can be supplied and withdrawn from.
Elements in supportedMarkets
must be a valid IonPool or an IDLE address. Valid IonPools require the base asset to be the same. Duplicate addition to the EnumerableSet will revert. The allocationCaps of the new markets being introduced must be set.
function addSupportedMarkets(
IIonPool[] memory marketsToAdd,
uint256[] memory allocationCaps,
IIonPool[] memory newSupplyQueue,
IIonPool[] memory newWithdrawQueue
)
external
onlyRole(OWNER_ROLE);
Parameters
marketsToAdd
IIonPool[]
Array of new markets to be added.
allocationCaps
uint256[]
Array of allocation caps for only the markets to be added.
newSupplyQueue
IIonPool[]
Desired supply queue of IonPools for all resulting supported markets.
newWithdrawQueue
IIonPool[]
Desired withdraw queue of IonPools for all resulting supported markets.
_addSupportedMarkets
function _addSupportedMarkets(
IIonPool[] memory marketsToAdd,
uint256[] memory allocationCaps,
IIonPool[] memory newSupplyQueue,
IIonPool[] memory newWithdrawQueue
)
internal;
removeSupportedMarkets
Removes a supported market and updates the supply and withdraw queues without the removed market.
The allocationCap values of the markets being removed are automatically deleted. Whenever a market is removed, the queues must be updated without the removed market.
function removeSupportedMarkets(
IIonPool[] calldata marketsToRemove,
IIonPool[] calldata newSupplyQueue,
IIonPool[] calldata newWithdrawQueue
)
external
onlyRole(OWNER_ROLE);
Parameters
marketsToRemove
IIonPool[]
Markets being removed.
newSupplyQueue
IIonPool[]
Desired supply queue of all supported markets after the removal.
newWithdrawQueue
IIonPool[]
Desired withdraw queue of all supported markets after the removal.
updateSupplyQueue
Update the order of the markets in which user deposits are supplied.
Each IonPool in the queue must be part of the supportedMarkets
set.
function updateSupplyQueue(IIonPool[] memory newSupplyQueue) external onlyRole(ALLOCATOR_ROLE);
Parameters
newSupplyQueue
IIonPool[]
The new supply queue ordering.
_updateSupplyQueue
function _updateSupplyQueue(IIonPool[] memory newSupplyQueue) internal;
updateWithdrawQueue
Update the order of the markets in which the deposits are withdrawn.
The IonPool in the queue must be part of the supportedMarkets
set.
function updateWithdrawQueue(IIonPool[] memory newWithdrawQueue) external onlyRole(ALLOCATOR_ROLE);
Parameters
newWithdrawQueue
IIonPool[]
The new withdraw queue ordering.
_updateWithdrawQueue
function _updateWithdrawQueue(IIonPool[] memory newWithdrawQueue) internal;
_validateQueueInput
*The input array contains ordered IonPools.
Must not contain duplicates.
Must be the same length as the
supportedMarkets
array.Must not contain indices that are out of bounds of the
supportedMarkets
EnumerableSet's underlying array. The above rule enforces that the queue must have all and only the elements in thesupportedMarkets
set.*
function _validateQueueInput(IIonPool[] memory queue) internal view;
Parameters
queue
IIonPool[]
The queue being validated.
updateAllocationCaps
Update allocation caps for specified IonPools or the IDLE pool.
The allocation caps are applied to pools in the order of the array within supportedMarkets
. The elements inside ionPools
must exist in supportedMarkets
. To update the IDLE
pool, use the IDLE
constant address.
function updateAllocationCaps(IIonPool[] calldata ionPools, uint256[] calldata newCaps) external onlyRole(OWNER_ROLE);
Parameters
ionPools
IIonPool[]
The array of IonPools whose caps will be updated.
newCaps
uint256[]
The array of new allocation caps to be applied.
reallocate
Reallocates the base asset supply position across the specified IonPools. This call will revert if the resulting allocation in an IonPool violates the pool's supply cap.
*Depending on the order of deposits and withdrawals to and from markets, the function could revert if there is not enough assets withdrawn to deposit later in the loop. A key invariant is that the total assets withdrawn should be equal to the total assets supplied. Otherwise, revert.
Negative value indicates a withdrawal.
Positive value indicates a supply.*
function reallocate(MarketAllocation[] calldata allocations) external onlyRole(ALLOCATOR_ROLE) nonReentrant;
Parameters
allocations
MarketAllocation[]
Array that indicates how much to deposit or withdraw from each market.
accrueFee
Manually accrues fees and mints shares to the fee recipient.
function accrueFee() external onlyRole(OWNER_ROLE) returns (uint256 newTotalAssets);
_supplyToIonPool
Iterates through the supply queue to deposit the desired amount of assets. Reverts if the deposit amount cannot be filled due to the allocation cap or the supply cap.
External functions calling this must be non-reentrant in case the underlying IonPool implements callback logic.
function _supplyToIonPool(uint256 assets) internal;
Parameters
assets
uint256
The amount of assets that will attempt to be supplied.
_withdrawFromIonPool
Iterates through the withdraw queue to withdraw the desired amount of assets. Will revert if there is not enough liquidity or if trying to withdraw more than the caller owns.
External functions calling this must be non-reentrant in case the underlying IonPool implements callback logic.
function _withdrawFromIonPool(uint256 assets) internal;
Parameters
assets
uint256
The desired amount of assets to be withdrawn.
deposit
Transfers the specified amount of assets from the sender, supplies into the underlying IonPool markets, and mints a corresponding amount of shares.
All incoming deposits are deposited in the order specified in the deposit queue.
function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares);
Parameters
assets
uint256
Amount of tokens to be deposited.
receiver
address
The address to receive the minted shares.
mint
Mints the specified amount of shares and deposits a corresponding amount of assets.
Converts the shares to assets and iterates through the deposit queue to allocate the deposit across the supported markets.
function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets);
Parameters
shares
uint256
The exact amount of shares to be minted.
receiver
address
The address to receive the minted shares.
withdraw
Withdraws specified amount of assets from IonPools and sends them to the receiver in exchange for burning the owner's vault shares.
All withdraws are withdrawn in the order specified in the withdraw queue. The owner needs to approve the caller to spend their shares.
function withdraw(
uint256 assets,
address receiver,
address owner
)
public
override
nonReentrant
returns (uint256 shares);
Parameters
assets
uint256
The exact amount of assets to be transferred out.
receiver
address
The receiver of the assets transferred.
owner
address
The owner of the vault shares.
redeem
Redeems the exact amount of shares and receives a corresponding amount of assets.
After withdrawing assets
, the user gets exact assets
out. But in the IonPool, the resulting total underlying claim may have decreased by a bit above the assets
amount due to rounding in the pool's favor. In that case, the resulting totalAssets()
will be smaller than just the newTotalAssets - assets
. Predicting the exact resulting totalAssets() requires knowing how much liquidity is being withdrawn from each pool, which is not possible to know until the actual iteration on the withdraw queue. So we acknowledge the dust difference here. If the lastTotalAssets
is slightly greater than the actual totalAssets
, the impact will be that the calculated interest accrued during fee distribution will be slightly less than the true value.
function redeem(
uint256 shares,
address receiver,
address owner
)
public
override
nonReentrant
returns (uint256 assets);
Parameters
shares
uint256
The exact amount of shares to be burned and redeemed.
receiver
address
The address that receives the transferred assets.
owner
address
The address that holds the shares to be redeemed.
decimals
Returns the decimals places of the token.
function decimals() public view override(ERC4626) returns (uint8);
maxDeposit
Returns the maximum amount of assets that the vault can supply on Ion.
The max deposit amount is limited by the vault's allocation cap and the underlying IonPools' supply caps.
function maxDeposit(address) public view override returns (uint256);
Returns
<none>
uint256
The max amount of assets that can be supplied.
maxMint
Returns the maximum amount of vault shares that can be minted.
Max mint is limited by the max deposit based on the Vault's allocation caps and the IonPools' supply caps. The conversion from max suppliable assets to shares preempts the shares minted from fee accrual.
function maxMint(address) public view override returns (uint256);
Returns
<none>
uint256
The max amount of shares that can be minted.
maxWithdraw
Returns the maximum amount of assets that can be withdrawn.
Max withdraw is limited by the owner's shares and the liquidity available to be withdrawn from the underlying IonPools. The max withdrawable claim is inclusive of accrued interest and the extra shares minted to the fee recipient.
function maxWithdraw(address owner) public view override returns (uint256 assets);
Parameters
owner
address
The address that holds the assets.
Returns
assets
uint256
The max amount of assets that can be withdrawn.
maxRedeem
Calculates the total withdrawable amount based on the available liquidity in the underlying pools and converts it to redeemable shares.
Max redeem is derived from Γ§onverting the _maxWithdraw
to shares. The conversion takes into account the total supply and total assets inclusive of accrued interest and the extra shares minted to the fee recipient.
function maxRedeem(address owner) public view override returns (uint256);
Parameters
owner
address
The address that holds the shares.
Returns
<none>
uint256
The max amount of shares that can be withdrawn.
totalAssets
Returns the total claim that the vault has across all supported IonPools.
IonPool.balanceOf
returns the rebasing balance of the lender receipt token that is pegged 1:1 to the underlying supplied asset.
function totalAssets() public view override returns (uint256 assets);
Returns
assets
uint256
The total assets held on the contract and inside the underlying pools by this vault.
previewDeposit
Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.
Inclusive of manager fee.
function previewDeposit(uint256 assets) public view override returns (uint256);
previewMint
Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.
Inclusive of manager fee.
function previewMint(uint256 shares) public view override returns (uint256);
previewWithdraw
Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.
Inclusive of manager fee.
function previewWithdraw(uint256 assets) public view override returns (uint256);
previewRedeem
Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions.
Inclusive of manager fee.
function previewRedeem(uint256 shares) public view override returns (uint256);
_decimalsOffset
function _decimalsOffset() internal view override returns (uint8);
_deposit
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override;
_withdraw
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override;
_maxDeposit
function _maxDeposit() internal view returns (uint256 maxDepositable);
_maxWithdraw
function _maxWithdraw(address owner)
internal
view
returns (uint256 assets, uint256 newTotalSupply, uint256 newTotalAssets);
_accrueFee
function _accrueFee() internal returns (uint256 newTotalAssets);
_accruedFeeShares
The total accrued vault revenue is the difference in the total iToken holdings from the last accrued timestamp and now.
function _accruedFeeShares() internal view returns (uint256 feeShares, uint256 newTotalAssets);
_convertToSharesWithFees
NOTE The IERC4626 natspec recommends that the _convertToAssets
and _convertToShares
"MUST NOT be inclusive of any fees that are charged against assets in the Vault." However, all deposit/mint/withdraw/redeem flow will accrue fees before processing user requests, so manager fee must be accounted for to accurately reflect the resulting state. All preview functions will rely on this WithFees
version of the _convertTo
function.
function _convertToSharesWithFees(uint256 assets, Math.Rounding rounding) internal view returns (uint256);
_convertToAssetsWithFees
NOTE The IERC4626 natspec recommends that the _convertToAssets
and _convertToShares
"MUST NOT be inclusive of any fees that are charged against assets in the Vault." However, all deposit/mint/withdraw/redeem flow will accrue fees before processing user requests, so manager fee must be accounted for to accurately reflect the resulting state. All preview functions will rely on this WithFees
version of the _convertTo
function.
function _convertToAssetsWithFees(uint256 shares, Math.Rounding rounding) internal view returns (uint256);
_convertToSharesWithTotals
Returns the amount of shares that the vault would exchange for the amount of assets
provided. This function is used to calculate the conversion between shares and assets with parameterizable total supply and total assets variables.
function _convertToSharesWithTotals(
uint256 assets,
uint256 newTotalSupply,
uint256 newTotalAssets,
Math.Rounding rounding
)
internal
view
returns (uint256);
_convertToAssetsWithTotals
Returns the amount of assets that the vault would exchange for the amount of shares
provided. This function is used to calculate the conversion between shares and assets with parameterizable total supply and total assets variables.
function _convertToAssetsWithTotals(
uint256 shares,
uint256 newTotalSupply,
uint256 newTotalAssets,
Math.Rounding rounding
)
internal
view
returns (uint256);
_updateLastTotalAssets
function _updateLastTotalAssets(uint256 newLastTotalAssets) internal;
_zeroFloorSub
function _zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z);
_simulateWithdrawIon
Emulates the actual _withdrawFromIonPool
accounting to predict accurately how much of the input assets will be left after withdrawing as much as it can. The difference between this return value and the input assets
is the exact amount that will be withdrawn.
function _simulateWithdrawIon(uint256 assets) internal view returns (uint256);
Returns
<none>
uint256
The remaining assets to be withdrawn. NOT the amount of assets that were withdrawn.
_withdrawable
The max amount of assets withdrawable from a given IonPool considering the vault's claim and the available liquidity. A minimum of this contract's total claim on the underlying and the available liquidity in the pool.
function _withdrawable(IIonPool pool) internal view returns (uint256);
Returns
<none>
uint256
The max amount of assets withdrawable from this IonPool.
_depositable
The max amount of assets depositable to a given IonPool. Depositing the minimum between the two diffs ensures that the deposit will not violate the allocation cap or the supply cap.
function _depositable(IIonPool pool) internal view returns (uint256);
Returns
<none>
uint256
The max amount of assets depositable to this IonPool.
getSupportedMarkets
Returns the array representation of the supportedMarkets
set.
function getSupportedMarkets() external view returns (address[] memory);
Returns
<none>
address[]
Array of supported IonPools.
containsSupportedMarket
Returns whether the market is part of the supportedMarkets
set.
function containsSupportedMarket(address pool) external view returns (bool);
Parameters
pool
address
The address of the IonPool to be checked.
Returns
<none>
bool
The pool is supported if true. If not, false.
supportedMarketsAt
Returns the element in the array representation of supportedMarkets
. index
must be strictly less than the length of the array.
function supportedMarketsAt(uint256 index) external view returns (address);
Parameters
index
uint256
The index to be queried on the supportedMarkets
array.
Returns
<none>
address
Address at the index of supportedMarkets
.
supportedMarketsIndexOf
Returns the index of the specified market in the array representation of supportedMarkets
.
The _positions
mapping inside the EnumerableSet.Set
returns the index of the element in the _values
array plus 1. The _positions
value of 0 means that the value is not in the set. If the value is not in the set, this call will revert. Otherwise, it will return the position - 1
value to return the index of the element in the array.
function supportedMarketsIndexOf(address pool) external view returns (uint256);
Parameters
pool
address
The address of the IonPool to be queried.
Returns
<none>
uint256
The index of the pool's location in the array. The return value will always be greater than zero as this function would revert if the market is not part of the set.
supportedMarketsLength
Length of the array representation of supportedMarkets
.
function supportedMarketsLength() external view returns (uint256);
Returns
<none>
uint256
The length of the supportedMarkets
array.
_supportedMarketsIndexOf
function _supportedMarketsIndexOf(address pool) internal view returns (uint256);
Events
UpdateSupplyQueue
event UpdateSupplyQueue(address indexed caller, IIonPool[] newSupplyQueue);
UpdateWithdrawQueue
event UpdateWithdrawQueue(address indexed caller, IIonPool[] newWithdrawQueue);
ReallocateWithdraw
event ReallocateWithdraw(IIonPool indexed pool, uint256 assets);
ReallocateSupply
event ReallocateSupply(IIonPool indexed pool, uint256 assets);
FeeAccrued
event FeeAccrued(uint256 feeShares, uint256 newTotalAssets);
UpdateLastTotalAssets
event UpdateLastTotalAssets(uint256 lastTotalAssets, uint256 newLastTotalAssets);
Errors
InvalidQueueLength
error InvalidQueueLength(uint256 queueLength, uint256 supportedMarketsLength);
AllocationCapExceeded
error AllocationCapExceeded(uint256 resultingSupplied, uint256 allocationCap);
InvalidReallocation
error InvalidReallocation(uint256 totalSupplied, uint256 totalWithdrawn);
InvalidMarketRemovalNonZeroSupply
error InvalidMarketRemovalNonZeroSupply(IIonPool pool);
InvalidUnderlyingAsset
error InvalidUnderlyingAsset(IIonPool pool);
MarketAlreadySupported
error MarketAlreadySupported(IIonPool pool);
MarketNotSupported
error MarketNotSupported(IIonPool pool);
AllSupplyCapsReached
error AllSupplyCapsReached();
NotEnoughLiquidityToWithdraw
error NotEnoughLiquidityToWithdraw();
InvalidIdleMarketRemovalNonZeroBalance
error InvalidIdleMarketRemovalNonZeroBalance();
InvalidQueueContainsDuplicates
error InvalidQueueContainsDuplicates();
MarketsAndAllocationCapLengthMustBeEqual
error MarketsAndAllocationCapLengthMustBeEqual();
IonPoolsArrayAndNewCapsArrayMustBeOfEqualLength
error IonPoolsArrayAndNewCapsArrayMustBeOfEqualLength();
InvalidFeePercentage
error InvalidFeePercentage();
MaxSupportedMarketsReached
error MaxSupportedMarketsReached();
Structs
MarketAllocation
struct MarketAllocation {
IIonPool pool;
int256 assets;
}
MarketsArgs
struct MarketsArgs {
IIonPool[] marketsToAdd;
uint256[] allocationCaps;
IIonPool[] newSupplyQueue;
IIonPool[] newWithdrawQueue;
}