Entry Vault
Introduction
The ConcreteMultiStrategyVault (vault) serves as the entry point for users into the earn system. The vault additionally serves as the source of truth for all accounting, including maintaining accurate accounting of the status of the assigned strategies.
The vault inherits from the following contracts:
ERC4626Upgradeable
Errors (custom error contract)
ReentrancyGuard
Pausable
OwnableUpgradeable
WithdrawalQueue (Custom withdrawal contract)
The vault maintains multiple instances of state, with the most important being:
struct GraduatedFee {
uint256 lowerBound;
uint256 upperBound;
uint64 fee;
}
struct VaultFees {
uint64 depositFee;
uint64 withdrawalFee;
uint64 protocolFee;
GraduatedFee[] performanceFee;
}
struct Allocation {
uint256 index;
uint256 amount;
}
struct Strategy {
IMockStrategy strategy;
Allocation allocation;
}
VaultFees private fees;
Strategy[] public strategies;
address public feeRecipient;
Functions that diverge from ERC4626 interface
The following functions diverge from the standard interface. These functions give our protocol the ability to handle multiple strategies, multiple fee tiers, altered accounting, etc.
Fee Logic
takeFees modifier
modifier takeFees()
The purpose of the takeFees modifier is to allow an easy method of automatically taking fees owed to the fee recipient. This function calls the accruedProtocolFee() and accruedPerformanceFee() in order to calculate the shares that are to be minted to the fee recipient.
accruedProtocolFee Function
function accruedProtocolFee() public view returns (uint256)
This function calculates the amount of total assets multiplied by the time since last update, multiplied by the protocol fee, divided by seconds in a year. The math in solidity looks like the following:
uint256(fees.protocolFee).mulDiv(
totalAssets() * (block.timestamp - feesUpdatedAt),
SECONDS_PER_YEAR,
Math.Rounding.Floor
) / 10000;
accruedPerformanceFee Function
This function calculates and returns the protocol share of yield generated in the strategies. There is an internal variable entitled highWaterMark
that indicates the highest value of an individual share. The function first checks that we have accrued more interest than we had previously. If so, a loop is entered. Because the performance fee is able to be configured based on a graduating fee (See struct above). The value is then returned to be utilized in the takeFees modifier. The calculation can be found below:
if (diff < fees.performanceFee[i].upperBound &&
diff > fees.performanceFee[i].lowerBound)
{
fee = ((shareValue - highWaterMark) * totalSupply()).mulDiv(
fees.performanceFee[i].fee, 10000, Math.Rounding.Floor
) / 1e18;
}
Strategy Logic
The vault is capable of utilizing multiple different strategies. Each strategy will be built individually, with a desired third party integration (i.e. One strategy could deposit into Aave, reaping the benefits of accruing interest as an LP).
The vault will track the strategies that have been enabled, as well as the allotment of underlying collateral by strategy.
addStrategy
/**
* @notice Adds a new strategy or replaces an existing one.
* @dev Can only be called by the vault owner. Validates the total allocation
* does not exceed 100%.
* Emits a `StrategyAdded` or `StrategyReplaced` event.
* @param index_ The index at which to add or replace the strategy. If replacing,
* this is the index of the existing strategy.
* @param replace_ A boolean indicating whether to replace an existing strategy.
* @param newStrategy_ The new strategy to add or replace the existing one with.
*/
function addStrategy(uint256 index_, bool replace_, Strategy calldata newStrategy_)
external
This function is responsible for adding a strategy to a vault. The code snippet above includes the natspec for this function.
You will notice that index_ is one of the params. This is used solely if the strategy is being replaced. If the strategy is simply being added you can pass zero into this param.
pushFundsToStrategie(s)
/**
* @notice Pushes funds from the vault into all strategies based on their allocation.
* @dev Can only be called by the vault owner. Reverts if the vault is idle.
*/
function pushFundsToStrategies() external onlyOwner {
if (vaultIdle) revert VaultIsIdle();
uint256 _totalAssets = totalAssets();
uint256 len = strategies.length;
for (uint256 i; i < len; i++) {
//We control both the length of the array and the external call
//slither-disable-next-line unused-return,calls-loop
strategies[i].strategy.deposit(
_totalAssets.mulDiv(strategies[i].allocation.amount, 10_000, Math.Rounding.Floor), address(this)
);
}
}
This function allows admin to push all funds from the vaults into the registered strategies. This can be used for re-balancing the vault.
There is a similar function that allows admin to push funds to a single strategy. The single difference in this interface is that admin will need to pass in the index of the strategy they wish to fund.
pullFundsFromStrategie(s)
/**
* @notice Pulls funds back from all strategies into the vault.
* @dev Can only be called by the vault owner.
*/
function pullFundsFromStrategies() external onlyOwner {
uint256 len = strategies.length;
for (uint256 i; i < len; i++) {
//We control both the length of the array and the external call
//slither-disable-next-line unused-return,calls-loop
strategies[i].strategy.redeem(strategies[i].strategy.balanceOf(address(this)), address(this), address(this));
}
}
This function allows admins to pull funds from strategies. The sister function to this allows admins to pull funds from a specific strategy. These features are designed to allow re-structuring of the vault.
Last updated