// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25
pragma solidity ^0.8.25;
import {BaseControlledAsyncRedeem} from "./BaseControlledAsyncRedeem.sol";
import {BaseERC7540, ERC20} from "./BaseERC7540.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
/// @notice Handles the initialize parameters of the vault
struct InitializeParams {
/// @notice The address of the asset that the vault will manage
address asset;
/// @notice The name of the vault
string name;
/// @notice The symbol of the vault
string symbol;
/// @notice The trusted manager of the vault (handles all sensitive management logic)
address owner;
/// @notice The limits of the vault
Limits limits;
/// @notice The fees of the vault
Fees fees;
}
/// @notice Stores the bounds of the vault
struct Bounds {
/// @notice Upper bound of the vault (will be used for future profit calculations of the manager)
uint256 upper;
/// @notice Lower bound of the vault (used on withdrawals to ensure an additional asset buffer between the reported totalAssets and the actual totalAssets)
uint256 lower;
}
/// @notice Stores the deposit limit and minAmounts of the vault
struct Limits {
/// @notice Maximum amount of assets that can be deposited into the vault
uint256 depositLimit;
/// @notice Minimum amount of shares that can be minted / redeemed from the vault
uint256 minAmount;
}
/// @notice Stores all fee related variables
struct Fees {
/// @notice Performance fee rate in 1e18 (100% = 1e18)
uint64 performanceFee;
/// @notice Management fee rate in 1e18 (100% = 1e18)
uint64 managementFee;
/// @notice Withdrawal incentive fee rate in 1e18 (100% = 1e18)
uint64 withdrawalIncentive;
/// @notice Timestamp of the last time the fees were updated (used for management fee calculations)
uint64 feesUpdatedAt;
/// @notice High water mark of the vault (used for performance fee calculations)
uint256 highWaterMark;
/// @notice Address of the fee recipient
address feeRecipient;
}
/**
* @title AsyncVault
* @author RedVeil
* @notice Abstract contract containing reusable logic that are the basis of ERC-7540 compliant async redeem vauls
* @notice Besides the basic logic for ERC-7540 this contract contains most other logic to manage a modern DeFi vault
* @dev Logic to account and manage assets must be implemented by inheriting contracts
*/
abstract contract AsyncVault is BaseControlledAsyncRedeem {
using FixedPointMathLib for uint256;
error ZeroAmount();
error Misconfigured();
/**
* @notice Constructor for AsyncVault
* @param params The initialization parameters
*/
constructor(
InitializeParams memory params
) BaseERC7540(params.owner, params.asset, params.name, params.symbol) {
_setLimits(params.limits);
_setFees(params.fees);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Deposit assets into the vault
* @param assets The amount of assets to deposit
* @return shares The amount of shares required
*/
function deposit(uint256 assets) external returns (uint256) {
return deposit(assets, msg.sender);
}
/**
* @notice Mint shares into the vault
* @param shares The amount of shares to mint
* @return assets The amount of assets received
*/
function mint(uint256 shares) external returns (uint256) {
return mint(shares, msg.sender);
}
/**
* @notice Withdraw assets from the vault
* @param assets The amount of assets to withdraw
* @return shares The amount of shares required
*/
function withdraw(uint256 assets) external returns (uint256) {
return withdraw(assets, msg.sender, msg.sender);
}
/**
* @notice Redeem shares from the vault
* @param shares The amount of shares to redeem
* @return assets The amount of assets received
*/
function redeem(uint256 shares) external returns (uint256) {
return redeem(shares, msg.sender, msg.sender);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Simulates a deposit into the vault and returns the amount of shares that would be received by the user
* @param assets The amount of assets to deposit
* @return shares The amount of shares that would be received by the user
* @dev This function will return 0 if the vault is paused or if the deposit doesnt meet the limits
*/
function previewDeposit(
uint256 assets
) public view override returns (uint256) {
Limits memory limits_ = limits;
uint256 shares = convertToShares(assets);
if (
paused ||
totalAssets() + assets > limits_.depositLimit ||
shares < limits_.minAmount
) return 0;
return super.previewDeposit(assets);
}
/**
* @notice Simulates a mint into the vault and returns the amount of assets required to mint the given amount of shares
* @param shares The amount of shares to mint
* @return assets The amount of assets required to mint the given amount of shares
* @dev This function will return 0 if the vault is paused or if the mint doesnt meet the limits
*/
function previewMint(
uint256 shares
) public view override returns (uint256) {
Limits memory limits_ = limits;
uint256 assets = convertToAssets(shares);
if (
paused ||
totalAssets() + assets > limits_.depositLimit ||
shares < limits_.minAmount
) return 0;
return super.previewMint(shares);
}
/**
* @notice Converts shares to assets based on a lower bound of totalAssets
* @param shares The amount of shares to convert
* @return lowerTotalAssets The lower bound value of assets that correspond to the given amount of shares
* @dev This function is used on redeem fulfillment to ensure an additional asset buffer between the reported totalAssets and the actual totalAssets.
* In most cases this will be the same as `convertToAssets` but same vaults might need to add a small buffer if they use volatile strategies or assets that are hard to sell.
*/
function convertToLowBoundAssets(
uint256 shares
) public view returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
uint256 assets = totalAssets().mulDivDown(1e18 - bounds.lower, 1e18);
return supply == 0 ? shares : shares.mulDivDown(assets, supply);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the maximum amount of assets that can be deposited into the vault
* @return assetsThe maxDeposit of the controller
* @dev Will return 0 if the vault is paused or if the deposit limit is reached
*/
function maxDeposit(address) public view override returns (uint256) {
uint256 assets = totalAssets();
uint256 depositLimit_ = limits.depositLimit;
if (depositLimit_ == type(uint256).max) return depositLimit_;
return (paused || assets >= depositLimit_) ? 0 : depositLimit_ - assets;
}
/**
* @notice Returns the maximum amount of shares that can be minted into the vault
* @return shares The maxMint of the controller
* @dev Will return 0 if the vault is paused or if the deposit limit is reached
* @dev Overflows if depositLimit is close to maxUint (convertToShares multiplies depositLimit with totalSupply)
*/
function maxMint(address) public view override returns (uint256) {
uint256 assets = totalAssets();
uint256 depositLimit_ = limits.depositLimit;
if (depositLimit_ == type(uint256).max) return depositLimit_;
return
(paused || assets >= depositLimit_)
? 0
: convertToShares(depositLimit_ - assets);
}
/*//////////////////////////////////////////////////////////////
REQUEST REDEEM LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Requests a redeem for the caller
* @param shares The amount of shares to redeem
* @return requestId The requestId of the redeem request
*/
function requestRedeem(uint256 shares) external returns (uint256) {
return requestRedeem(shares, msg.sender, msg.sender);
}
/**
* @notice Requests a redeem of shares from the vault
* @param shares The amount of shares to redeem
* @param controller The user that will be receiving pending shares
* @param owner The owner of the shares to redeem
* @return requestId The requestId of the redeem request
* @dev This redeem request is added to any pending redeem request of the controller
* @dev This function will revert if the shares are less than the minAmount
*/
function requestRedeem(
uint256 shares,
address controller,
address owner
) public override returns (uint256 requestId) {
require(shares >= limits.minAmount, "ERC7540Vault/min-amount");
return _requestRedeem(shares, controller, owner);
}
/*//////////////////////////////////////////////////////////////
FULFILL REDEEM LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Fulfills a redeem request of the controller to allow the controller to withdraw their assets
* @param shares The amount of shares to redeem
* @param controller The controller to redeem for
* @return assets The amount of assets received
* @dev This function will revert if the shares are less than the minAmount
* @dev This function will also take the withdrawal incentive fee from the assets to incentivse the manager to fulfill the request
*/
function fulfillRedeem(
uint256 shares,
address controller
) external override returns (uint256 assets) {
// Take fees before fulfilling the redeem
_takeFees();
// Using the lower bound totalAssets ensures that even with volatile strategies and market conditions we will have sufficient assets to cover the redeem
assets = convertToLowBoundAssets(shares);
// Calculate the withdrawal incentive fee from the assets
Fees memory fees_ = fees;
uint256 fees = assets.mulDivDown(
uint256(fees_.withdrawalIncentive),
1e18
);
// Burn controller's shares
_burn(address(this), shares);
// Fulfill the redeem request
_fulfillRedeem(assets - fees, shares, controller);
// Send the withdrawal incentive fee to the fee recipient
handleWithdrawalIncentive(fees, fees_.feeRecipient);
}
/**
* @notice Fulfills multiple redeem requests of the controller to allow the controller to withdraw their assets
* @param shares The amount of shares to redeem
* @param controllers The controllers to redeem for
* @return total The total amount of assets received
* @dev This function will revert if the shares and controllers arrays are not the same length
* @dev This function will also take the withdrawal incentive fee from the assets to incentivse the manager to fulfill the requests
*/
function fulfillMultipleRedeems(
uint256[] memory shares,
address[] memory controllers
) external returns (uint256 total) {
if (shares.length != controllers.length) revert Misconfigured();
// Take fees before fulfilling the redeem
_takeFees();
// cache the fees
Fees memory fees_ = fees;
uint256 totalShares;
uint256 totalFees;
for (uint256 i; i < shares.length; i++) {
// Using the lower bound totalAssets ensures that even with volatile strategies and market conditions we will have sufficient assets to cover the redeem
uint256 assets = convertToLowBoundAssets(shares[i]);
// Calculate the withdrawal incentive fee from the assets
uint256 fees = assets.mulDivDown(
uint256(fees_.withdrawalIncentive),
1e18
);
// Fulfill the redeem request
_fulfillRedeem(assets - fees, shares[i], controllers[i]);
// Add to the total assets and fees
total += assets;
totalFees += fees;
totalShares += shares[i];
}
// Burn controller's shares
_burn(address(this), totalShares);
// Send the withdrawal incentive fee to the fee recipient
handleWithdrawalIncentive(totalFees, fees_.feeRecipient);
return total;
}
/**
* @notice Handles the withdrawal incentive fee by sending it to the fee recipient
* @param fee The amount of fee to send
* @param feeRecipient The address to send the fee to
* @dev This function is expected to be overriden in inheriting contracts
*/
function handleWithdrawalIncentive(
uint256 fee,
address feeRecipient
) internal virtual {
if (fee > 0) SafeTransferLib.safeTransfer(asset, feeRecipient, fee);
}
/*//////////////////////////////////////////////////////////////
ERC-4626 OVERRIDES
//////////////////////////////////////////////////////////////*/
/**
* @notice Takes fees before a withdraw (if the contract is not paused)
* @dev This function is expected to be overriden in inheriting contracts
*/
function beforeWithdraw(uint256 assets, uint256) internal virtual override {
if (!paused) _takeFees();
}
/**
* @notice Takes fees before a deposit
* @dev This function is expected to be overriden in inheriting contracts
*/
function beforeDeposit(uint256 assets, uint256) internal virtual override {
// deposit and mint already have the `whenNotPaused` modifier so we don't need to check it here
_takeFees();
}
/*//////////////////////////////////////////////////////////////
BOUND LOGIC
//////////////////////////////////////////////////////////////*/
Bounds public bounds;
event BoundsUpdated(Bounds prev, Bounds next);
/// @notice Returns the bounds of the vault
function getBounds() public view returns (Bounds memory) {
return bounds;
}
/**
* @notice Sets the bounds of the vault to ensure that even with volatile strategies and market conditions we will have sufficient assets to cover the redeem
* @param bounds_ The bounds to set
* @dev This function will revert if the bounds are greater than or equal to 1e18
*/
function setBounds(Bounds memory bounds_) external onlyOwner {
if (bounds_.lower >= 1e18 || bounds_.upper >= 1e18)
revert Misconfigured();
emit BoundsUpdated(bounds, bounds_);
bounds = bounds_;
}
/*//////////////////////////////////////////////////////////////
FEE LOGIC
//////////////////////////////////////////////////////////////*/
Fees public fees;
event FeesUpdated(Fees prev, Fees next);
error InvalidFee(uint256 fee);
/// @notice Returns the fees parameters of the vault
function getFees() public view returns (Fees memory) {
return fees;
}
/// @notice Returns the accrued fees of the vault
function accruedFees() public view returns (uint256) {
Fees memory fees_ = fees;
return _accruedFees(fees_);
}
/// @dev Internal function to calculate the accrued fees
function _accruedFees(Fees memory fees_) internal view returns (uint256) {
return _accruedPerformanceFee(fees_) + _accruedManagementFee(fees_);
}
/**
* @notice Performance fee that has accrued since last fee harvest.
* @return accruedPerformanceFee In underlying `asset` token.
* @dev Performance fee is based on a high water mark value. If vault share value has increased above the
* HWM in a fee period, issue fee shares to the vault equal to the performance fee.
*/
function _accruedPerformanceFee(
Fees memory fees_
) internal view returns (uint256) {
uint256 shareValue = convertToAssets(10 ** asset.decimals());
uint256 performanceFee = uint256(fees_.performanceFee);
return
performanceFee > 0 && shareValue > fees_.highWaterMark
? performanceFee.mulDivUp(
(shareValue - fees_.highWaterMark) * totalSupply,
(10 ** (18 + asset.decimals()))
)
: 0;
}
/**
* @notice Management fee that has accrued since last fee harvest.
* @return accruedManagementFee In underlying `asset` token.
* @dev Management fee is annualized per minute, based on 525,600 minutes per year. Total assets are calculated using
* the average of their current value and the value at the previous fee harvest checkpoint. This method is similar to
* calculating a definite integral using the trapezoid rule.
*/
function _accruedManagementFee(
Fees memory fees_
) internal view returns (uint256) {
uint256 managementFee = uint256(fees_.managementFee);
return
managementFee > 0
? managementFee.mulDivDown(
totalAssets() * (block.timestamp - fees_.feesUpdatedAt),
365.25 days // seconds per year
) / 1e18
: 0;
}
/**
* @notice Sets the fees of the vault
* @param fees_ The fees to set
* @dev This function will revert if the fees are greater than 20% performanceFee, 5% managementFee, or 5% withdrawalIncentive
* @dev This function will also take the fees before setting them to ensure the new fees rates arent applied to any pending fees
*/
function setFees(Fees memory fees_) public onlyOwner whenNotPaused {
_takeFees();
_setFees(fees_);
}
/// @dev Internal function to set the fees
function _setFees(Fees memory fees_) internal {
// Dont take more than 20% performanceFee, 5% managementFee, 5% withdrawalIncentive
if (
fees_.performanceFee > 2e17 ||
fees_.managementFee > 5e16 ||
fees_.withdrawalIncentive > 5e16
) revert Misconfigured();
if (fees_.feeRecipient == address(0)) revert Misconfigured();
// Dont rely on user input here
fees_.feesUpdatedAt = uint64(block.timestamp);
// initialise or copy current HWM
if (fees.highWaterMark == 0) {
fees_.highWaterMark = convertToAssets(10 ** asset.decimals()); // from constructor
} else {
fees_.highWaterMark = fees.highWaterMark; // from setFees
}
emit FeesUpdated(fees, fees_);
fees = fees_;
}
/**
* @notice Mints fees as shares of the vault to the fee recipient
* @dev It will also update the all other fee related variables
*/
function takeFees() external whenNotPaused {
_takeFees();
}
/// @dev Internal function to take the fees
function _takeFees() internal {
Fees memory fees_ = fees;
uint256 fee = _accruedFees(fees_);
uint256 shareValue = convertToAssets(10 ** asset.decimals());
if (shareValue > fees_.highWaterMark) fees.highWaterMark = shareValue;
if (fee > 0) _mint(fees_.feeRecipient, convertToShares(fee));
fees.feesUpdatedAt = uint64(block.timestamp);
}
/*//////////////////////////////////////////////////////////////
LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
Limits public limits;
event LimitsUpdated(Limits prev, Limits next);
/**
* @notice Sets the deposit limit and minAmounts of the vault to limit user exposure to strategy risks
* @param limits_ The limits to set
*/
function setLimits(Limits memory limits_) external onlyOwner {
_setLimits(limits_);
}
/// @dev Internal function to set the limits
function _setLimits(Limits memory limits_) internal {
// TODO this can lock user deposits if lowered too much
emit LimitsUpdated(limits, limits_);
limits = limits_;
}
}
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25
pragma solidity ^0.8.25;
import {BaseERC7540} from "./BaseERC7540.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {IERC7540Redeem} from "ERC-7540/interfaces/IERC7540.sol";
/// @notice Stores the requestBalance of a controller
struct RequestBalance {
/// @notice The amount of shares that have been requested to be redeemed
uint256 pendingShares;
/// @notice The timestamp of the last redeem request (will be used to ensure timely fulfillment of redeem requests)
uint256 requestTime;
/// @notice The amount of shares that have been freed up by a fulfilled redeem request
uint256 claimableShares;
/// @notice The amount of assets that have been freed up by a fulfilled redeem request
uint256 claimableAssets;
}
/**
* @title BaseControlledAsyncRedeem
* @author RedVeil
* @notice Abstract contract containing reusable logic for controlled async redeem flows
* @dev Based on https://github.com/ERC4626-Alliance/ERC-7540-Reference/blob/main/src/BaseControlledAsyncRedeem.sol
*/
abstract contract BaseControlledAsyncRedeem is BaseERC7540, IERC7540Redeem {
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
ERC4626 OVERRIDDEN LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Deposit assets into the vault
* @param assets The amount of assets to deposit
* @param receiver The address to receive the shares
* @return shares The amount of shares received
* @dev This function is synchronous and will revert if the vault is paused
* @dev It will first use claimable balances of previous redeem requests before transferring assets from the sender
*/
function deposit(
uint256 assets,
address receiver
) public override whenNotPaused returns (uint256 shares) {
// Additional logic for inheriting contracts
beforeDeposit(assets, shares);
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
SafeTransferLib.safeTransferFrom(
asset,
msg.sender,
address(this),
assets
);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
// Additional logic for inheriting contracts
afterDeposit(assets, shares);
}
/**
* @notice Mints shares from the vault
* @param shares The amount of shares to mint
* @param receiver The address to receive the shares
* @return assets The amount of assets deposited
* @dev This function is synchronous and will revert if the vault is paused
* @dev It will first use claimable balances of previous redeem requests before minting shares
*/
function mint(
uint256 shares,
address receiver
) public override whenNotPaused returns (uint256 assets) {
// Additional logic for inheriting contracts
beforeDeposit(assets, shares);
require(shares != 0, "ZERO_SHARES");
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
SafeTransferLib.safeTransferFrom(
asset,
msg.sender,
address(this),
assets
);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
// Additional logic for inheriting contracts
afterDeposit(assets, shares);
}
/// @dev Additional logic for inheriting contracts before depositing
function beforeDeposit(uint256 assets, uint256 shares) internal virtual {}
/**
* @notice Withdraws assets from the vault which have beenpreviously freed up by a fulfilled redeem request
* @param assets The amount of assets to withdraw
* @param receiver The address to receive the assets
* @param controller The controller to withdraw from
* @return shares The amount of shares burned
* @dev This function is asynchronous and will not revert if the vault is paused
* @dev msg.sender must be the controller or an operator for the controller
* @dev Requires sufficient claimableAssets in the controller's requestBalance
*/
function withdraw(
uint256 assets,
address receiver,
address controller
) public virtual override returns (uint256 shares) {
require(
controller == msg.sender || isOperator[controller][msg.sender],
"ERC7540Vault/invalid-caller"
);
require(assets != 0, "ZERO_ASSETS");
RequestBalance storage currentBalance = requestBalances[controller];
shares = assets.mulDivUp(
currentBalance.claimableShares,
currentBalance.claimableAssets
);
// Modify the currentBalance state accordingly
_withdrawClaimableBalance(assets, currentBalance);
// Additional logic for inheriting contracts
beforeWithdraw(assets, shares);
// Transfer assets to the receiver
SafeTransferLib.safeTransfer(asset, receiver, assets);
emit Withdraw(msg.sender, receiver, controller, assets, shares);
// Additional logic for inheriting contracts
afterWithdraw(assets, shares);
}
/**
* @notice Modifies the currentBalance state to reflect a withdrawal of claimableAssets
* @param assets The amount of assets to withdraw
* @param currentBalance The requestBalance of the controller
* @dev Claiming partially introduces precision loss. The user therefore receives a rounded down amount,
* while the claimable balance is reduced by a rounded up amount.
*/
function _withdrawClaimableBalance(
uint256 assets,
RequestBalance storage currentBalance
) internal {
uint256 sharesUp = assets.mulDivUp(
currentBalance.claimableShares,
currentBalance.claimableAssets
);
currentBalance.claimableAssets -= assets;
currentBalance.claimableShares = currentBalance.claimableShares >
sharesUp
? currentBalance.claimableShares - sharesUp
: 0;
}
/**
* @notice Redeems shares from the vault which have beenpreviously freed up by a fulfilled redeem request
* @param shares The amount of shares to redeem
* @param receiver The address to receive the assets
* @param controller The controller to redeem from
* @return assets The amount of assets received
* @dev This function is asynchronous and will not revert if the vault is paused
* @dev msg.sender must be the controller or an operator for the controller
* @dev Requires sufficient claimableShares in the controller's requestBalance
*/
function redeem(
uint256 shares,
address receiver,
address controller
) public virtual override returns (uint256 assets) {
require(
controller == msg.sender || isOperator[controller][msg.sender],
"ERC7540Vault/invalid-caller"
);
require(shares != 0, "ZERO_SHARES");
RequestBalance storage currentBalance = requestBalances[controller];
assets = shares.mulDivDown(
currentBalance.claimableAssets,
currentBalance.claimableShares
);
// Modify the currentBalance state accordingly
_redeemClaimableBalance(shares, currentBalance);
// Additional logic for inheriting contracts
beforeWithdraw(assets, shares);
// Transfer assets to the receiver
SafeTransferLib.safeTransfer(asset, receiver, assets);
emit Withdraw(msg.sender, receiver, controller, assets, shares);
// Additional logic for inheriting contracts
afterWithdraw(assets, shares);
}
/**
* @notice Modifies the currentBalance state to reflect a withdrawal of claimableAssets
* @param shares The amount of shares to redeem
* @param currentBalance The requestBalance of the controller
* @dev Claiming partially introduces precision loss. The user therefore receives a rounded down amount,
* while the claimable balance is reduced by a rounded up amount.
*/
function _redeemClaimableBalance(
uint256 shares,
RequestBalance storage currentBalance
) internal {
uint256 assetsUp = shares.mulDivUp(
currentBalance.claimableAssets,
currentBalance.claimableShares
);
currentBalance.claimableAssets = currentBalance.claimableAssets >
assetsUp
? currentBalance.claimableAssets - assetsUp
: 0;
currentBalance.claimableShares -= shares;
}
/// @dev Additional logic for inheriting contracts after withdrawing
function afterWithdraw(uint256 assets, uint256 shares) internal virtual {}
/*//////////////////////////////////////////////////////////////
ACCOUNTNG LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev controller => requestBalance
mapping(address => RequestBalance) public requestBalances;
/**
* @notice Returns the requestBalance of a controller
* @param controller The controller to get the requestBalance of
* @return requestBalance The requestBalance of the controller
*/
function getRequestBalance(
address controller
) public view returns (RequestBalance memory) {
return requestBalances[controller];
}
/**
* @notice Returns the requested shares for redeem that have not yet been fulfilled of a controller
* @param controller The controller to get the pendingShares of
* @return pendingShares The pendingShares of the controller
*/
function pendingRedeemRequest(
uint256,
address controller
) public view returns (uint256) {
return requestBalances[controller].pendingShares;
}
/**
* @notice Returns the shares that have been freed up by a fulfilled redeem request of a controller
* @param controller The controller to get the claimableShares of
* @return claimableShares The claimableShares of the controller
*/
function claimableRedeemRequest(
uint256,
address controller
) public view returns (uint256) {
return requestBalances[controller].claimableShares;
}
/**
* @notice Simulates a deposit into the vault and returns the amount of shares that would be received by the user
* @param assets The amount of assets to deposit
* @return shares The amount of shares that would be received by the user
* @dev This function will return 0 if the vault is paused
*/
function previewDeposit(
uint256 assets
) public view virtual override returns (uint256) {
return paused ? 0 : super.previewDeposit(assets);
}
/**
* @notice Simulates a mint into the vault and returns the amount of assets required to mint the given amount of shares
* @param shares The amount of shares to mint
* @return assets The amount of assets required to mint the given amount of shares
* @dev This function will return 0 if the vault is paused
*/
function previewMint(
uint256 shares
) public view virtual override returns (uint256) {
return paused ? 0 : super.previewMint(shares);
}
/// @dev Previewing withdraw is not supported for async flows (we would require the controller to be known which we do not have in ERC4626)
function previewWithdraw(
uint256
) public pure virtual override returns (uint256) {
revert("ERC7540Vault/async-flow");
}
/// @dev Previewing redeem is not supported for async flows (we would require the controller to be known which we do not have in ERC4626)
function previewRedeem(
uint256
) public pure virtual override returns (uint256 assets) {
revert("ERC7540Vault/async-flow");
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the maximum amount of assets that can be deposited into the vault
* @return assets The maxDeposit of the controller
* @dev Will return 0 if the vault is paused
*/
function maxDeposit(
address
) public view virtual override returns (uint256) {
return paused ? 0 : type(uint256).max;
}
/**
* @notice Returns the maximum amount of shares that can be minted into the vault
* @return shares The maxMint of the controller
* @dev Will return 0 if the vault is paused
*/
function maxMint(address) public view virtual override returns (uint256) {
return paused ? 0 : type(uint256).max;
}
/**
* @notice Returns the maximum amount of assets that can be withdrawn from the vault
* @param controller The controller to get the maxWithdraw of
* @return assets The maxWithdraw of the controller
* @dev This is simply the claimableAssets of the controller (i.e. the assets that have been freed up by a fulfilled redeem request)
*/
function maxWithdraw(
address controller
) public view virtual override returns (uint256) {
return requestBalances[controller].claimableAssets;
}
/**
* @notice Returns the maximum amount of shares that can be redeemed from the vault
* @param controller The controller to get the maxRedeem of
* @return shares The maxRedeem of the controller
* @dev This is simply the claimableShares of the controller (i.e. the shares that have been freed up by a fulfilled redeem request)
*/
function maxRedeem(
address controller
) public view virtual override returns (uint256) {
return requestBalances[controller].claimableShares;
}
/*//////////////////////////////////////////////////////////////
REQUEST REDEEM LOGIC
//////////////////////////////////////////////////////////////*/
event RedeemRequested(
address indexed controller,
address indexed owner,
uint256 requestId,
address sender,
uint256 shares
);
/**
* @notice Requests a redeem of shares from the vault
* @param shares The amount of shares to redeem
* @param controller The user that will be receiving pending shares
* @param owner The owner of the shares to redeem
* @return requestId The requestId of the redeem request
* @dev This redeem request is added to any pending redeem request of the controller
*/
function requestRedeem(
uint256 shares,
address controller,
address owner
) external virtual returns (uint256 requestId) {
return _requestRedeem(shares, controller, owner);
}
/// @dev Internal function to request a redeem
function _requestRedeem(
uint256 shares,
address controller,
address owner
) internal returns (uint256 requestId) {
require(
owner == msg.sender || isOperator[owner][msg.sender],
"ERC7540Vault/invalid-owner"
);
require(
ERC20(address(this)).balanceOf(owner) >= shares,
"ERC7540Vault/insufficient-balance"
);
require(shares != 0, "ZERO_SHARES");
// Transfer shares from owner to vault (these will be burned on withdrawal)
SafeTransferLib.safeTransferFrom(this, owner, address(this), shares);
// Update the controller's requestBalance
RequestBalance storage currentBalance = requestBalances[controller];
currentBalance.pendingShares += shares;
currentBalance.requestTime = block.timestamp;
emit RedeemRequested(controller, owner, REQUEST_ID, msg.sender, shares);
return REQUEST_ID;
}
/*//////////////////////////////////////////////////////////////
CANCEL REDEEM REQUEST LOGIC
//////////////////////////////////////////////////////////////*/
event RedeemRequestCanceled(
address indexed controller,
address indexed receiver,
uint256 shares
);
/**
* @notice Cancels a redeem request of the controller
* @param controller The controller to cancel the redeem request of
* @dev This will transfer the pending shares back to the msg.sender
*/
function cancelRedeemRequest(address controller) external virtual {
return _cancelRedeemRequest(controller, msg.sender);
}
/**
* @notice Cancels a redeem request of the controller
* @param controller The controller to cancel the redeem request of
* @param receiver The receiver of the pending shares
* @dev This will transfer the pending shares back to the receiver
*/
function cancelRedeemRequest(
address controller,
address receiver
) public virtual {
return _cancelRedeemRequest(controller, receiver);
}
/// @dev Internal function to cancel a redeem request
function _cancelRedeemRequest(
address controller,
address receiver
) internal virtual {
require(
controller == msg.sender || isOperator[controller][msg.sender],
"ERC7540Vault/invalid-caller"
);
// Get the pending shares
RequestBalance storage currentBalance = requestBalances[controller];
uint256 shares = currentBalance.pendingShares;
require(shares > 0, "ERC7540Vault/no-pending-request");
// Transfer the pending shares back to the receiver
SafeTransferLib.safeTransfer(ERC20(address(this)), receiver, shares);
// Update the controller's requestBalance
currentBalance.pendingShares = 0;
currentBalance.requestTime = 0;
emit RedeemRequestCanceled(controller, receiver, shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT FULFILLMENT LOGIC
//////////////////////////////////////////////////////////////*/
event RedeemRequestFulfilled(
address indexed controller,
address indexed fulfiller,
uint256 shares,
uint256 assets
);
/**
* @notice Fulfills a redeem request of the controller to allow the controller to withdraw their assets
* @param shares The amount of shares to redeem
* @param controller The controller to redeem for
* @return assets The amount of assets claimable by the controller
*/
function fulfillRedeem(
uint256 shares,
address controller
) external virtual returns (uint256) {
uint256 assets = convertToAssets(shares);
// Burn controller's shares
_burn(address(this), shares);
return _fulfillRedeem(assets, shares, controller);
}
/// @dev Internal function to fulfill a redeem request
function _fulfillRedeem(
uint256 assets,
uint256 shares,
address controller
) internal virtual returns (uint256) {
if (assets == 0 || shares == 0) revert("ZERO_SHARES");
RequestBalance storage currentBalance = requestBalances[controller];
// Check that there are pending shares to fulfill
require(
currentBalance.pendingShares != 0 &&
shares <= currentBalance.pendingShares,
"ZERO_SHARES"
);
// Additional logic for inheriting contracts
beforeFulfillRedeem(assets, shares);
// Update the controller's requestBalance
currentBalance.claimableShares += shares;
currentBalance.claimableAssets += assets;
currentBalance.pendingShares -= shares;
// Reset the requestTime if there are no more pending shares
if (currentBalance.pendingShares == 0) currentBalance.requestTime = 0;
emit RedeemRequestFulfilled(controller, msg.sender, shares, assets);
// Additional logic for inheriting contracts
afterFulfillRedeem(assets, shares);
return assets;
}
/// @dev Additional logic for inheriting contracts before fulfilling a redeem request
function beforeFulfillRedeem(
uint256 assets,
uint256 shares
) internal virtual {}
/// @dev Additional logic for inheriting contracts after fulfilling a redeem request
function afterFulfillRedeem(
uint256 assets,
uint256 shares
) internal virtual {}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Check if the contract supports an interface
* @param interfaceId The interface ID to check
* @return exists True if the contract supports the interface, false otherwise
*/
function supportsInterface(
bytes4 interfaceId
) public pure virtual override returns (bool) {
return
interfaceId == type(IERC7540Redeem).interfaceId ||
super.supportsInterface(interfaceId);
}
}
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25
pragma solidity ^0.8.25;
import {ERC4626} from "solmate/tokens/ERC4626.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {Owned} from "src/utils/Owned.sol";
import {Pausable} from "src/utils/Pausable.sol";
import {IERC7540Operator} from "ERC-7540/interfaces/IERC7540.sol";
import {IERC7575} from "ERC-7540/interfaces/IERC7575.sol";
import {IERC165} from "ERC-7540/interfaces/IERC7575.sol";
/**
* @title BaseERC7540
* @author RedVeil
* @notice Abstract contract containing reusable logic for ERC-7540 - https://eips.ethereum.org/EIPS/eip-7540
* @notice Based on https://github.com/ERC4626-Alliance/ERC-7540-Reference/blob/main/src/BaseERC7540.sol
*/
abstract contract BaseERC7540 is
ERC4626,
Owned,
ReentrancyGuard,
Pausable,
IERC7540Operator
{
/// @dev Assume requests are non-fungible and all have ID = 0
uint256 internal constant REQUEST_ID = 0;
/// @dev Required for IERC7575
address public share = address(this);
/**
* @notice Constructor for BaseERC7540
* @param _owner The permissioned owner of the vault (controls all management functions)
* @param _asset The address of the underlying asset
* @param _name The name of the vault
* @param _symbol The symbol of the vault
*/
constructor(
address _owner,
address _asset,
string memory _name,
string memory _symbol
) Owned(_owner) ERC4626(ERC20(_asset), _name, _symbol) {}
/*//////////////////////////////////////////////////////////////
ROLE LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev role => account => approved
mapping(bytes32 => mapping(address => bool)) public hasRole;
event RoleUpdated(bytes32 role, address account, bool approved);
/**
* @notice Update the role for an account
* @param role The role to update
* @param account The account to update
* @param approved The approval status to set
*/
function updateRole(
bytes32 role,
address account,
bool approved
) public onlyOwner {
hasRole[role][account] = approved;
emit RoleUpdated(role, account, approved);
}
/**
* @notice Modifier to check if the caller has the specified role or is the owner
* @param role The role to check
*/
modifier onlyRoleOrOwner(bytes32 role) {
require(
hasRole[role][msg.sender] || msg.sender == owner,
"BaseERC7540/not-authorized"
);
_;
}
/*//////////////////////////////////////////////////////////////
PAUSING LOGIC
//////////////////////////////////////////////////////////////*/
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @notice Pause Deposits. Caller must be owner or have the PAUSER_ROLE
function pause() external override onlyRoleOrOwner(PAUSER_ROLE) {
_pause();
}
/// @notice Unpause Deposits. Caller must be owner
function unpause() external override onlyOwner {
_unpause();
}
/*//////////////////////////////////////////////////////////////
ERC7540 LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev controller => operator => approved
mapping(address => mapping(address => bool)) public isOperator;
/**
* @notice Set the approval status for an operator
* @param operator The operator to set
* @param approved The approval status to set
* @dev Operators are approved to requestRedeem,withdraw and redeem for the msg.sender using the balance of msg.sender
*/
function setOperator(
address operator,
bool approved
) public virtual returns (bool success) {
require(
msg.sender != operator,
"ERC7540Vault/cannot-set-self-as-operator"
);
isOperator[msg.sender][operator] = approved;
emit OperatorSet(msg.sender, operator, approved);
success = true;
}
/*//////////////////////////////////////////////////////////////
EIP-7441 LOGIC
//////////////////////////////////////////////////////////////*/
mapping(address controller => mapping(bytes32 nonce => bool used))
public authorizations;
/**
* @notice Authorize an operator for a controller
* @param controller The controller to authorize the operator for
* @param operator The operator to authorize
* @param approved The approval status to set
* @param nonce The nonce to use for the authorization
* @param deadline The deadline for the authorization
* @param signature The signature to verify the authorization
* @dev Operators are approved to requestRedeem,withdraw and redeem for the msg.sender using the balance of msg.sender
*/
function authorizeOperator(
address controller,
address operator,
bool approved,
bytes32 nonce,
uint256 deadline,
bytes memory signature
) public virtual returns (bool success) {
require(
controller != operator,
"ERC7540Vault/cannot-set-self-as-operator"
);
require(block.timestamp <= deadline, "ERC7540Vault/expired");
require(
!authorizations[controller][nonce],
"ERC7540Vault/authorization-used"
);
authorizations[controller][nonce] = true;
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)"
),
controller,
operator,
approved,
nonce,
deadline
)
)
)
),
v,
r,
s
);
require(
recoveredAddress != address(0) && recoveredAddress == controller,
"INVALID_SIGNER"
);
isOperator[controller][operator] = approved;
emit OperatorSet(controller, operator, approved);
success = true;
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Check if the contract supports an interface
* @param interfaceId The interface ID to check
* @return True if the contract supports the interface, false otherwise
*/
function supportsInterface(
bytes4 interfaceId
) public pure virtual returns (bool) {
return
interfaceId == type(IERC7575).interfaceId ||
interfaceId == type(IERC7540Operator).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
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.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, 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(y, 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
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.5.0;
interface IERC7540Operator {
/**
* @dev The event emitted when an operator is set.
*
* @param controller The address of the controller.
* @param operator The address of the operator.
* @param approved The approval status.
*/
event OperatorSet(address indexed controller, address indexed operator, bool approved);
/**
* @dev Sets or removes an operator for the caller.
*
* @param operator The address of the operator.
* @param approved The approval status.
* @return Whether the call was executed successfully or not
*/
function setOperator(address operator, bool approved) external returns (bool);
/**
* @dev Returns `true` if the `operator` is approved as an operator for an `controller`.
*
* @param controller The address of the controller.
* @param operator The address of the operator.
* @return status The approval status
*/
function isOperator(address controller, address operator) external view returns (bool status);
}
interface IERC7540Deposit {
event DepositRequest(
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets
);
/**
* @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) external returns (uint256 requestId);
/**
* @dev Returns the amount of requested assets in Pending state.
*
* - MUST NOT include any assets in Claimable state for deposit or mint.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function pendingDepositRequest(uint256 requestId, address controller)
external
view
returns (uint256 pendingAssets);
/**
* @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint.
*
* - MUST NOT include any assets in Pending state.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function claimableDepositRequest(uint256 requestId, address controller)
external
view
returns (uint256 claimableAssets);
/**
* @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) external returns (uint256 shares);
/**
* @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) external returns (uint256 assets);
}
interface IERC7540Redeem {
event RedeemRequest(
address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets
);
/**
* @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) external returns (uint256 requestId);
/**
* @dev Returns the amount of requested shares in Pending state.
*
* - MUST NOT include any shares in Claimable state for redeem or withdraw.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function pendingRedeemRequest(uint256 requestId, address controller)
external
view
returns (uint256 pendingShares);
/**
* @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw.
*
* - MUST NOT include any shares in Pending state for redeem or withdraw.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
*/
function claimableRedeemRequest(uint256 requestId, address controller)
external
view
returns (uint256 claimableShares);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.5.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
interface IERC7575 is IERC165 {
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 address of the share token
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function share() external view returns (address shareTokenAddress);
/**
* @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 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 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: GPL-2.0-or-later
pragma solidity >=0.8.0;
/// @title IPriceOracle
/// @custom:security-contact security@euler.xyz
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice Common PriceOracle interface.
interface IPriceOracle {
/// @notice Get the name of the oracle.
/// @return The name of the oracle.
function name() external view returns (string memory);
/// @notice One-sided price: How much quote token you would get for inAmount of base token, assuming no price spread.
/// @param inAmount The amount of `base` to convert.
/// @param base The token that is being priced.
/// @param quote The token that is the unit of account.
/// @return outAmount The amount of `quote` that is equivalent to `inAmount` of `base`.
function getQuote(
uint256 inAmount,
address base,
address quote
) external view returns (uint256 outAmount);
/// @notice Two-sided price: How much quote token you would get/spend for selling/buying inAmount of base token.
/// @param inAmount The amount of `base` to convert.
/// @param base The token that is being priced.
/// @param quote The token that is the unit of account.
/// @return bidOutAmount The amount of `quote` you would get for selling `inAmount` of `base`.
/// @return askOutAmount The amount of `quote` you would spend for buying `inAmount` of `base`.
function getQuotes(
uint256 inAmount,
address base,
address quote
) external view returns (uint256 bidOutAmount, uint256 askOutAmount);
}
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25
pragma solidity ^0.8.25;
import {AsyncVault, InitializeParams} from "./AsyncVault.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {IPriceOracle} from "src/interfaces/IPriceOracle.sol";
/**
* @title OracleVault
* @author RedVeil
* @notice ERC-7540 (https://eips.ethereum.org/EIPS/eip-7540) compliant async redeem vault using a PushOracle for pricing and a Safe for managing assets
* @dev Oracle and safe security is handled in other contracts. We simply assume they are secure and don't implement any further checks in this contract
*/
contract OracleVault is AsyncVault {
address public safe;
/**
* @notice Constructor for the OracleVault
* @param params The parameters to initialize the vault with
* @param oracle_ The oracle to use for pricing
* @param safe_ The safe which will manage the assets
*/
constructor(
InitializeParams memory params,
address oracle_,
address safe_
) AsyncVault(params) {
if (safe_ == address(0) || oracle_ == address(0))
revert Misconfigured();
safe = safe_;
oracle = IPriceOracle(oracle_);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
IPriceOracle public oracle;
/// @notice Total amount of underlying `asset` token managed by the safe.
function totalAssets() public view override returns (uint256) {
return oracle.getQuote(totalSupply, share, address(asset));
}
/*//////////////////////////////////////////////////////////////
ERC-4626 OVERRIDES
//////////////////////////////////////////////////////////////*/
/// @dev Internal function to handle the deposit and mint
function afterDeposit(uint256 assets, uint256) internal override {
// Deposit and mint already have the `whenNotPaused` modifier so we don't need to check it here
_takeFees();
// Transfer assets to the safe
SafeTransferLib.safeTransfer(asset, safe, assets);
}
/*//////////////////////////////////////////////////////////////
BaseControlledAsyncRedeem OVERRIDES
//////////////////////////////////////////////////////////////*/
/// @dev Internal function to transfer assets from the safe to the vault before fulfilling a redeem
function beforeFulfillRedeem(uint256 assets, uint256) internal override {
SafeTransferLib.safeTransferFrom(
asset,
safe,
address(this),
assets
);
}
/*//////////////////////////////////////////////////////////////
AsyncVault OVERRIDES
//////////////////////////////////////////////////////////////*/
/// @dev Internal function to handle the withdrawal incentive
function handleWithdrawalIncentive(
uint256 fee,
address feeRecipient
) internal override {
if (fee > 0)
// Transfer the fee from the safe to the fee recipient
SafeTransferLib.safeTransferFrom(
asset,
safe,
feeRecipient,
fee
);
}
}
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25
pragma solidity ^0.8.25;
// https://docs.synthetix.io/contracts/source/contracts/owned
contract Owned {
address public owner;
address public nominatedOwner;
event OwnerNominated(address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
constructor(address _owner) {
require(_owner != address(0), "Owned/owner-zero");
owner = _owner;
emit OwnerChanged(address(0), _owner);
}
function nominateNewOwner(address _owner) external virtual onlyOwner {
nominatedOwner = _owner;
emit OwnerNominated(_owner);
}
function acceptOwnership() external virtual {
require(
msg.sender == nominatedOwner,
"Owned/not-nominated"
);
emit OwnerChanged(owner, nominatedOwner);
owner = nominatedOwner;
nominatedOwner = address(0);
}
modifier onlyOwner() {
_onlyOwner();
_;
}
function _onlyOwner() private view {
require(
msg.sender == owner,
"Owned/not-owner"
);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized Pausable for smart contracts.
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Pausable.sol)
abstract contract Pausable {
bool public paused;
event Paused(address account);
event Unpaused(address account);
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function _requireNotPaused() internal view virtual {
if (paused) {
revert("Pausable/paused");
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused) {
revert("Pausable/not-paused");
}
}
function _pause() internal virtual whenNotPaused {
paused = true;
emit Paused(msg.sender);
}
function _unpause() internal virtual whenPaused {
paused = false;
emit Unpaused(msg.sender);
}
function pause() external virtual whenNotPaused {
_pause();
}
function unpause() external virtual whenPaused {
_unpause();
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
{
"compilationTarget": {
"src/vaults/multisig/phase1/OracleVault.sol": "OracleVault"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":ERC-7540-Reference/=lib/ERC-7540-Reference/src/",
":ERC-7540/=lib/ERC-7540-Reference/src/",
":bitlib/=lib/solidity-bytes-utils/contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
":safe-smart-account/=lib/safe-smart-account/contracts/",
":solady/=lib/solady/src/",
":solidity-bytes-utils/=lib/solidity-bytes-utils/contracts/",
":solmate/=lib/solmate/src/",
":weiroll/=lib/weiroll/src/"
]
}
[{"inputs":[{"components":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address","name":"owner","type":"address"},{"components":[{"internalType":"uint256","name":"depositLimit","type":"uint256"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"internalType":"struct Limits","name":"limits","type":"tuple"},{"components":[{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"uint64","name":"managementFee","type":"uint64"},{"internalType":"uint64","name":"withdrawalIncentive","type":"uint64"},{"internalType":"uint64","name":"feesUpdatedAt","type":"uint64"},{"internalType":"uint256","name":"highWaterMark","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"internalType":"struct Fees","name":"fees","type":"tuple"}],"internalType":"struct InitializeParams","name":"params","type":"tuple"},{"internalType":"address","name":"oracle_","type":"address"},{"internalType":"address","name":"safe_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"InvalidFee","type":"error"},{"inputs":[],"name":"Misconfigured","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"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":[{"components":[{"internalType":"uint256","name":"upper","type":"uint256"},{"internalType":"uint256","name":"lower","type":"uint256"}],"indexed":false,"internalType":"struct Bounds","name":"prev","type":"tuple"},{"components":[{"internalType":"uint256","name":"upper","type":"uint256"},{"internalType":"uint256","name":"lower","type":"uint256"}],"indexed":false,"internalType":"struct Bounds","name":"next","type":"tuple"}],"name":"BoundsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","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":[{"components":[{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"uint64","name":"managementFee","type":"uint64"},{"internalType":"uint64","name":"withdrawalIncentive","type":"uint64"},{"internalType":"uint64","name":"feesUpdatedAt","type":"uint64"},{"internalType":"uint256","name":"highWaterMark","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"indexed":false,"internalType":"struct Fees","name":"prev","type":"tuple"},{"components":[{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"uint64","name":"managementFee","type":"uint64"},{"internalType":"uint64","name":"withdrawalIncentive","type":"uint64"},{"internalType":"uint64","name":"feesUpdatedAt","type":"uint64"},{"internalType":"uint256","name":"highWaterMark","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"indexed":false,"internalType":"struct Fees","name":"next","type":"tuple"}],"name":"FeesUpdated","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"depositLimit","type":"uint256"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"indexed":false,"internalType":"struct Limits","name":"prev","type":"tuple"},{"components":[{"internalType":"uint256","name":"depositLimit","type":"uint256"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"indexed":false,"internalType":"struct Limits","name":"next","type":"tuple"}],"name":"LimitsUpdated","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":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","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":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"RedeemRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"RedeemRequestCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":true,"internalType":"address","name":"fulfiller","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"}],"name":"RedeemRequestFulfilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"controller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"RedeemRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"RoleUpdated","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":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","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"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"accruedFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","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":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"},{"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"authorizations","outputs":[{"internalType":"bool","name":"used","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"authorizeOperator","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bounds","outputs":[{"internalType":"uint256","name":"upper","type":"uint256"},{"internalType":"uint256","name":"lower","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"},{"internalType":"address","name":"receiver","type":"address"}],"name":"cancelRedeemRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"cancelRedeemRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"controller","type":"address"}],"name":"claimableRedeemRequest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToLowBoundAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","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":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fees","outputs":[{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"uint64","name":"managementFee","type":"uint64"},{"internalType":"uint64","name":"withdrawalIncentive","type":"uint64"},{"internalType":"uint64","name":"feesUpdatedAt","type":"uint64"},{"internalType":"uint256","name":"highWaterMark","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"shares","type":"uint256[]"},{"internalType":"address[]","name":"controllers","type":"address[]"}],"name":"fulfillMultipleRedeems","outputs":[{"internalType":"uint256","name":"total","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"controller","type":"address"}],"name":"fulfillRedeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getBounds","outputs":[{"components":[{"internalType":"uint256","name":"upper","type":"uint256"},{"internalType":"uint256","name":"lower","type":"uint256"}],"internalType":"struct Bounds","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFees","outputs":[{"components":[{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"uint64","name":"managementFee","type":"uint64"},{"internalType":"uint64","name":"withdrawalIncentive","type":"uint64"},{"internalType":"uint64","name":"feesUpdatedAt","type":"uint64"},{"internalType":"uint256","name":"highWaterMark","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"internalType":"struct Fees","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"getRequestBalance","outputs":[{"components":[{"internalType":"uint256","name":"pendingShares","type":"uint256"},{"internalType":"uint256","name":"requestTime","type":"uint256"},{"internalType":"uint256","name":"claimableShares","type":"uint256"},{"internalType":"uint256","name":"claimableAssets","type":"uint256"}],"internalType":"struct RequestBalance","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"limits","outputs":[{"internalType":"uint256","name":"depositLimit","type":"uint256"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":"nominateNewOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracle","outputs":[{"internalType":"contract IPriceOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"controller","type":"address"}],"name":"pendingRedeemRequest","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":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","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":"uint256","name":"shares","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"requestBalances","outputs":[{"internalType":"uint256","name":"pendingShares","type":"uint256"},{"internalType":"uint256","name":"requestTime","type":"uint256"},{"internalType":"uint256","name":"claimableShares","type":"uint256"},{"internalType":"uint256","name":"claimableAssets","type":"uint256"}],"stateMutability":"view","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":"uint256","name":"shares","type":"uint256"}],"name":"requestRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"safe","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"upper","type":"uint256"},{"internalType":"uint256","name":"lower","type":"uint256"}],"internalType":"struct Bounds","name":"bounds_","type":"tuple"}],"name":"setBounds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint64","name":"performanceFee","type":"uint64"},{"internalType":"uint64","name":"managementFee","type":"uint64"},{"internalType":"uint64","name":"withdrawalIncentive","type":"uint64"},{"internalType":"uint64","name":"feesUpdatedAt","type":"uint64"},{"internalType":"uint256","name":"highWaterMark","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"internalType":"struct Fees","name":"fees_","type":"tuple"}],"name":"setFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"depositLimit","type":"uint256"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"internalType":"struct Limits","name":"limits_","type":"tuple"}],"name":"setLimits","outputs":[],"stateMutability":"nonpayable","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":"share","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"takeFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","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":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"updateRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","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"}]