Skip to main content

Vaults Version 3

Version 3 yVaults iterates on Version 2 by increasing robustness and developing Yearn’s path towards further decentralization, while keeping the same proven product (yield-bearing tokens) that abstract builders and users from the hard work of yield farming. Version 3 will both be able to have the same functionality as Version 2, but with many more added benefits and improvements to continue to grow the Yearn ecosystem.

V3 also sees the introduction of "Tokenized Strategies". In V3 the strategies are now capable of being standalone 4626 vaults themselves. These single-strategy vaults can be used as stand-alone vaults or due to the 4626 standard easily added as a strategy to any of the multi-strategy "Meta Vaults".

Version 3 yVaults improves on Version 2 by:

  • Increasing vault modularity, allowing for smaller and safer pieces of code.
  • Simplify strategy creation, empowering strategists and reducing the chance for errors.
  • More strategy functionality by implementing the Tokenized Strategy.
  • All ERC-4626 implementations for easy integrations.
  • Decreased gas costs to report profits and update debts.
  • Adding new products like ySwaps which increases swap efficiency and yJuniorTranches which allows for different risk profiles.

Smart Modules

Smart Modules implement core vault logic that will be iterated until they can be made immutable. If any Smart Module fails, the vault can live without them just enough to return funds to depositors. When Version 3 launches, the Smart Modules will replicate Version 2 vault behavior.

  • Debt Allocator: Can efficiently allocate debt to different strategies added to a meta vault to the best yield opportunities.
  • Accountant: Handles changing fees for vault operations.

Periphery Contracts

Periphery contracts are a separate layer of optional contracts to use with vaults and strategies. They are not required but they facilitate building around yVaults.

  • Router: Wrapper that handles deposits and withdrawals to/from all vaults and strategies.
  • Yearn Lens: Information aggregator for off-chain apps.
  • APY TWAP Oracle: Reliable source of Yearn vaults’ past performance.
  • ySwaps: Internal swap system. Reduces slippage thus improving net APY.
  • Registry: Handles adding and tracking strategies and vaults.
  • HealthCheck: Guardrail vault operations so that profit & loss reporting is always under acceptable values.
  • Swappers: Pre-built contracts for strategies to inherit to easily implement their desired swap logic.
  • APR Oracles: Retrieve the expected current APY on chain for different strategies to properly allocate debt.
  • And any others you can come up with!

Version 3 yVault Specification

Overview

The Vault code has been designed as a non-opinionated system to distribute funds of depositors into different opportunities (aka Strategies) and manage accounting in a robust way. That's all.

Depositors receive shares (aka vaults tokens) proportional to their deposit amount. Vault tokens are yield-bearing and can be redeemed at any time to get back deposit plus any yield generated.

The Vault does not have a preference on any of the dimensions that should be considered when operating a vault:

  • Decentralization: Roles can be filled by any address (e.g. EOA, smart contract, multi-sig).
  • Liquidity: Vault can have 0 liquidity or be fully liquid. It will depend on parameters and strategies added.
  • Security: Vault managers can choose what strategies to add and how to do that process.
  • Automation: All the required actions to maintain the vault can be called by bots or manually, depending on periphery implementation.

The compromises will come with the implementation of periphery contracts fulfilling the roles in the Vault.

This allows different players to deploy their own version and implement their own periphery contracts (or not use any at all)

Example periphery contracts: 
- Emergency module: it receives deposits of Vault Shares and allows the contract to call the shutdown function after a certain % of total Vault Shares have been deposited
- Debt Allocator: a smart contract that incentivizes APY / debt allocation optimization by rewarding the best debt allocation (see [yStarkDebtAllocator](https://github.com/jmonteer/ystarkdebtallocator))
- Strategy Staking Module: a smart contract that allows players to sponsor specific strategies (so that they are added to the vault) by staking their YFI, making money if they do well and losing money if they don't
- ...

Definitions

  • Asset: Any ERC20-compliant token
  • Shares: ERC20-compliant token that tracks Asset balance in the vault for every distributor. Named yv<Asset_Symbol>
  • Depositor: Account that holds Shares
  • Strategy: Smart contract that is used to deposit in Protocols to generate yield
  • Vault: ERC4626 compliant Smart contract that receives Assets from Depositors to then distribute them among the different Strategies added to the vault, managing accounting and Assets distribution.
  • Role: the different flags an Account can have in the Vault so that the Account can do certain specific actions. Can be fulfilled by a smart contract or an EOA.
  • Accountant: smart contract that receives P&L reporting and returns shares and refunds to the strategy

Deployment

We expect all the vaults available to be deployed from a Factory Contract, publicly available and callable.

Players deploying "branded" vaults (e.g. Yearn) will use a separate registry to allow permissioned endorsement of vaults for their product

When deploying a new vault, it requires the following parameters:

  • asset: address of the ERC20 token that can be deposited in the vault
  • name: name of Shares as described in ERC20
  • symbol: symbol of Shares ERC20
  • role_manager: account that can assign and revoke Roles
  • profit_max_unlock_time: max amount of time profit will be locked before being distributed

Normal Operation

Deposits / Mints

Users can deposit asset tokens (ASSET) to receive yvTokens (SHARES).

Deposits are limited under depositLimit and shutdown parameters. Read below for details.

Withdrawals / Redeems

Users can redeem their shares at any point in time if there is liquidity available.

Optionally, a user can specify a list of strategies to withdraw from. If a list of strategies is passed, the vault will try to withdraw from them.

If a user passed array is not defined, the redeem function will use the default_queue.

In order to properly comply with the ERC-4626 standard and still allow losses, both withdraw and redeem have an additional optional parameter of 'maxLoss' that can be used. The default for 'maxLoss' is 0 (i.e. revert if any loss) for withdraws, and 10_000 (100%) for redeems.

If not enough funds have been recovered to honor the full request within the maxLoss, the transaction will revert.

Vault Shares

Vault shares are ERC20 transferable yield-bearing tokens.

They are ERC4626 compliant. Please read ERC4626 compliance to understand the implications.

Accounting

The vault will evaluate profit and losses from the strategies.

This is done by comparing the current debt of the strategy with the total assets the strategy is reporting to have.

If totalAssets < currentDebt: the vault will record a loss If totalAssets > currentDebt: the vault will record a profit

Both loss and profit will impact strategy's debt, increasing the debt (current debt + profit) if there are profits, decreasing its debt (current debt - loss) if there are losses.

Fees

Fee assessment and distribution are handled by the Accountant module.

It will report the amount of fees that need to be charged and the vault will issue shares for that amount of fees.

There is also an optional protocol_fee that can be charged based on the configuration of the VaultFactory.vy

Profit distribution

Profit from different process_report calls will accumulate in a buffer. This buffer will be linearly unlocked over the locking period seconds at profit_distribution_rate.

Profits will be locked for a max period of time of profit_max_unlock_time seconds and will be gradually distributed. To avoid spending too much gas for profit unlock, the amount of time a profit will be locked is a weighted average between the new profit and the previous profit.

new_locking_period = locked_profit * pending_time_to_unlock + new_profit * PROFIT_MAX_UNLOCK_TIME / (locked_profit + new_profit)

new_profit_distribution_rate = (locked_profit + new_profit) / new_locking_period

Losses will be offset by locked profit, if possible.

The issue of new shares due to fees will also unlock profit so that PPS does not go down.

Both of these offsets will prevent front running (as the profit was already earned and was not distributed yet).

Vault Management

Vault management is split into function-specific roles. Each permissioned function has its own corresponding role.

This means roles can be combined all to one address, each distributed to separate addresses or any combination in between

Roles

Vault functions that are permissioned will be callable by accounts that hold specific roles.

These are:

  • ADD_STRATEGY_MANAGER: role that can add strategies to the vault.
  • REVOKE_STRATEGY_MANAGER: role that can remove strategies from the vault.
  • FORCE_REVOKE_MANAGER: role that can force remove a strategy causing a loss.
  • ACCOUNTANT_MANAGER: role that can set the accountant that assesses fees.
  • QUEUE_MANAGER: role that can set the default withdrawal queue.
  • REPORTING_MANAGER: role that calls report for strategies.
  • DEBT_MANAGER: role that adds and removes debt from strategies.
  • MAX_DEBT_MANAGER: role that can set the max debt for a strategy.
  • DEPOSIT_LIMIT_MANAGER: role that sets deposit limit for the vault.
  • MINIMUM_IDLE_MANAGER: role that sets the minimum total idle the vault should keep.
  • PROFIT_UNLOCK_MANAGER: role that sets the profit_max_unlock_time.
  • DEBT_PURCHASER: can purchase bad debt from the vault.
  • EMERGENCY_MANAGER: role that can shut down vault in an emergency.

Every role can be filled by an EOA, multi-sig or other smart contracts. Each role can be filled by several accounts.

The account that manages roles is a single account, set in role_manager.

This role_manager can be an EOA, a multi-sig or a Governance Module that relays calls.

Strategy Management

This responsibility is taken by callers with ADD_STRATEGY_MANAGER, REVOKE_STRATEGY_MANAGER and FORCE_REVOKE_MANAGER roles

A vault can have strategies added, removed or forcefully removed

Added strategies will be eligible to receive funds from the vault, when the max_debt is set to > 0

Revoked strategies will return all debt and stop being eligible to receive more. It can only be done when the strategy's current_debt is 0

Force revoking a strategy is only used in cases of a faulty strategy that cannot otherwise have its current_debt reduced to 0. Force revoking a strategy will result in a loss being reported by the vault.

Setting the periphery contracts

The accountant can each be set by the ACCOUNTANT_MANAGER.

The contract is not needed for the vault to function but are recommended for optimal use.

Reporting profits

The REPORTING_MANAGER is in charge of calling process_report() for each strategy in the vault according to its own timeline

This call will do the necessary accounting and profit locking for the individual strategy as well as charge fees

Debt Management

This responsibility is taken by callers with DEBT_MANAGER role

This role can increase or decrease strategies-specific debt.

The vault sends and receives funds to/from strategies. The function updateDebt(strategy, target_debt) will set the current_debt of the strategy to target_debt (if possible)

If the strategy currently has less debt than the target_debt, the vault will send funds to it.

The vault checks that the minimumTotalIdle parameter is respected (i.e. there's at least a certain amount of funds in the vault).

If the strategy has more debt than the max_debt, the vault will request the funds back. These funds may be locked in the strategy, which will result in the strategy returning less funds than requested by the vault.

Setting maximum debt for a specific strategy

The MAX_DEBT_MANAGER can set the maximum amount of tokens the vault will allow a strategy to owe at any moment in time.

Stored in strategies[strategy].max_debt

When a debt re-balance is triggered, the Vault will cap the new target debt to this number (max_debt)

Setting the deposit limit

The DEPOSIT_LIMIT_MANAGER is in charge of setting the deposit_limit for the vault.

On deployment deposit_limit defaults to 0 and will need to be increased to make the vault functional

Setting minimum idle funds

The MINIMUM_IDLE_MANAGER can specify how many funds the vault should try to have reserved to serve withdrawal requests

These funds will remain in the vault unless requested by a Depositor

It is recommended that if no queue_manager is set some amount of funds should remain idle to service withdrawals

Setting the profit unlock period

The PROFIT_UNLOCK_MANAGER is in charge of updating and setting the profit_max_unlock_time which controls how fast profits will unlock

This can be customized based on the vault based on aspects such as number of strategies, TVL, expected returns etc.

Setting the default queue

The QUEUE_MANAGER has the option to set a custom default_queue if desired. The vault will arrange the default queue automatically based only on the order that strategies were added to the vault. If a different order is desired the queue manager role can set a custom queue.

All strategies in the default queue must have been previously added to the vault.

Buying Debt

The DEBT_PURCHASER role can buy debt from the vault in return for an equal amount of asset.

This should only ever be used in the case where governance wants to purchase a set amount of bad debt from the vault in order to not report a loss.

It still relies on convertToShares() so will only be viable if the conversion does not reflect and large negative realized loss from the strategy.

Shutting down the vault

In an emergency the EMERGENCY_MANAGER can shut down the vault

This will also give the EMERGENCY_MANAGER and the DEBT_MANAGER roles as well so funds can start to be returned from the strategies

Strategy Minimum API

Strategies are completely independent smart contracts that can be implemented following the proposed template or in any other way.

In any case, to be compatible with the vault, they need to implement the following functions, which are a subset of ERC4626 vaults:

  • asset(): view returning underlying asset
  • totalAssets(): view returning the current amount of assets. It can include rewards valued in asset ¡
  • maxDeposit(address): view returning the amount max that the strategy can take safely
  • deposit(assets, receiver): deposits assets amount of tokens into the strategy. it can be restricted to vault only or be open
  • maxWithdraw(address): view returning how many assets can the vault take from the vault at any given point in time
  • withdraw(assets, receiver, owner): withdraws assets amount of tokens from the strategy
  • redeem(shares, receiver, owner): redeems shares of the strategy for the underlying asset.
  • balanceOf(address): return the number of shares of the strategy that the address has
  • convertToAssets(shares: uint256): Converts shares into the corresponding amount of asset.
  • convertToShares(assets: uint256): Converts assets into the corresponding amount of shares.
  • previewWithdraw(assets: uint256): Converst assets into the corresponding amount of shares rounding up.

This means that the vault can deposit into any ERC4626 vault but also that a non-compliant strategy can be implemented provided that these functions have been implemented (even in a non ERC4626 compliant way).

ERC4626 compliance

Vault Shares are ERC4626 compliant.

The most important implication is that withdraw and redeem functions as presented in ERC4626, with the ability to add two additional non-standard options.

  1. max_loss: The amount in basis points that the withdrawer will accept as a loss. I.E. 100 = 1% loss accepted.
  2. strategies: This is an array of strategies to use as the withdrawal queue instead of the default queue.

Shutdown mode

In the case the current roles stop fulfilling their responsibilities or something else happens, the EMERGENCY_MANAGER can shut down the vault.

The shutdown mode should be the last option in an emergency as it is irreversible.

Deposits

_Light emergency_: Deposits can be paused by setting depositLimit to 0

_Shutdown mode_: Deposits are not allowed

Withdrawals

Withdrawals can't be paused under any circumstance

Accounting

Shutdown mode does not affect accounting

Debt rebalance

_Light emergency_: Setting minimumTotalIdle to MAX_UINT256 will result in the vault requesting the debt back from strategies. This would stop new strategies from getting funded too, as the vault prioritizes minimumTotalIdle

_Shutdown mode_: All strategies maxDebt is set to 0. Strategies will return funds as soon as they can.

Relevant emergency

In case the current roles stop fulfilling their responsibilities or something else happens, the EMERGENCY_MANAGER can shut down the vault.

The shutdown mode should be the last option in an emergency as it is irreversible.

During shutdown mode, the vault will try to get funds back from every strategy as soon as possible.

No strategies can be added during the shutdown

Any relevant role will start pointing to the EMERGENCY_MANAGER in case new permissioned allowed actions need to be taken.

Yearn V3 Tokenized Strategy Specification

Overview

The Yearn V3 "Tokenized Strategy" goal is to make it as easy as possible for any person or protocol to create and deploy their own ERC-4626 compliant single strategy vault. It uses an immutable proxy pattern to outsource all of the standardized 4626 and other vault logic to one implementation contract that all Strategies deployed on a specific chain use through delegatecall.

This makes the strategy-specific contract as simple and specific to that yield-generating task as possible and allows for anyone to simply plug their version into a permissionless, secure and optimized 4626 compliant base that handles all risky and complicated code.

Definitions

  • Asset: Any ERC20-compliant token
  • Shares: ERC20-compliant token that tracks Asset balance in the strategy for every distributor.
  • Strategy: ERC4626 compliant smart contract that receives Assets from Depositors (vault or otherwise) to deposit in any external protocol to generate yield.
  • Tokenized Strategy: The implementation contract that all strategies delegateCall to for the standard ERC4626 and profit locking functions.
  • BaseTokenizedStrategy: The abstract contract that a strategy should inherit from that handles all communication with the Tokenized Strategy contract.
  • Strategist: The developer of a specific strategy.
  • Depositor: Account that holds Shares
  • Vault: Or "Meta Vault" is an ERC4626 compliant Smart contract that receives Assets from Depositors to then distribute them among the different Strategies added to the vault, managing accounting and Assets distribution.
  • Management: The owner of the specific strategy that can set fees, profit unlocking time etc.
  • Keeper: the address of a contract allowed to call report() and tend() on a strategy.
  • Factory: The factory that all meta vaults of a specific API version are cloned from that also controls the protocol fee amount and recipient.

Storage

In order to standardize all high-risk and complex logic associated with ERC4626, ERC20 and profit locking, all core logic has been moved to the 'TokenizedStrategy.sol' and is used by each strategy through a fallback function that delegatecall's this contract to do all necessary checks, logic and storage updates for the strategy.

The TokenizedStrategy will only need to be deployed once on each chain and can then be used by an unlimited number of strategies. Allowing the BaseTokenizedStrategy.sol to be much smaller, simpler and cheaper to deploy.

Using delegate call the external TokenizedStrategyy will be able to read and write to any and all of the strategies specific storage variables during all calls. This does open the strategy up to the possibility of storage collisions due to non-standardized storage calls and means extra precautions need to be taken when reading and writing to storage.

In order to limit the strategists need to think about their storage variables all TokenizedStrategy specific variables are held within and controlled by the TokenizedStrategy. A StrategyData struct is held at a custom storage location that is high enough that no normal implementation should be worried about hitting.

This means all high risk storage updates will always be handled by the TokenizedStrategy, should not be able to be overridden by a reckless strategist and will be entirely standardized across every strategy deployed, no matter the chain or specific implementation.

BaseTokenizedStrategy

The base tokenized strategy is a simple abstract contract to be inherited by the strategist that handles all communication with the TokenizedStrategy.

Modifiers

onlySelf: This modifier is placed on any callback functions for the TokenizedStrategy to call during deposits, withdraws, reports and tends. The modifier should revert if msg.sender is not equal to itself. In order for a call to be forwarded to the TokenizedStrategy it must not be defined in the Strategy and hit the fallback function which will delegatecall the TokenizedStrategy. If within that call, the TokenizedStrategy makes an external static call back to the BaseTokenizedStrategy the msg.sender of that call will be the original caller, which should be the Strategy itself.

OnlyManagement: Should be placed on functions that only the Strategies specific management address can call. This uses the isManagement(address) function defined in TokenizedStrategy by sending the original msg.sender address.

onlyKeepers: Should be placed on functions that only the Strategies specific management or keeper can call. This uses the isManagementOrKeeper(address) defined in TokenizedStrategy sending the original msg.sender address.

Variables

tokenizedStrategyAddress: This is the address the fallback function will use to delegatecall to and is set before deployment to a constant so it can never be changed.

TokenizedStrategy: This is an immutable set on deployment setting an ITokenizedStrategy interface to address(this). The variable should be used in a similar manner as a linked library would be to have a simple method to read from the Strategies storage internally. Setting it to address(this) means anything using this variable will static call itself which should hit the fallback and then delegatecall the TokenizedStrategy retrieving the correct variables.

asset: The immutable address of the underlying asset being used.

Functions

The majority of function in the BaseTokenizedStrategy are either external functions with onlySelf modifiers used for the TokenizedStrategy to call. Or the internal functions that correspond to those external functions that should or can be overridden by a strategist with the strategy specific logic.

deployFunds(uint256)/_DeployFunds(uint256): Called by the TokenizedStrategy during deposits into the strategy to tell the strategy it can deposit up to the amount passed in as a parameter if desired.

freeFunds(uint256)/_freeFunds(uint256): Called by the TokenizedStrategy during withdraws to get the amount of the uint256 parameter freed up in order to process the withdraw.

harvestAndReport()/_harvestAndReport(): Called during reports to tell the strategy a trusted address has called it and to harvest any rewards re-deploy any loose funds and return the actual amount of funds the strategy holds.

tendThis(uint256)/_tend(uint256): Called by the TokenizedStrategy during tend calls to tell the strategy a trusted address has called tend and it has the uint256 parameter of loose asset available to deposit. NOTE: we use tendThis to avoid function signature collisions so that tend will be forwarded to the TokenizedStrategy.

tendTrigger(): View function to return if a tend call is needed.

availableDepositLimt(address)/availableWithdrawLimit(address): Optional functions a strategist can override that default to uint256 max to implement any deposit or withdraw limits.

shutdownWithdraw(uint256)/_emergencyWithdraw(uint256): Optional function for a strategist to implement that will allow management to manually withdraw a specified amount from the yield source if a strategy is shutdown in the case of emergencies.

_init(...): Used only once during initialization to manually delegatecall the TokenizedStrategy to tell it to set up the storage for a new strategy.

TokenizedStrategy

The tokenized strategy contract should implement all ERC-4626, ERC-20, ERC-2612 and custom TokenizedStrategy specific report and tending logic within it.

For deposits, withdraws, report, tend and emergency withdraw calls it casts address(this) into a custom IBaseTokenizedStrategy() interface to static call back the initial calling contract when it needs to interact with the Strategy.

Normal Operation

The TokenizedStrategy is responsible for handling the logic associated with all the following functionality.

Deposits / Mints

Users can deposit ASSET tokens to receive shares.

Deposits are limited by the maxAvailableDeposit function that can be changed by the strategist if non uint256.max values are desired.

Withdrawals / Redeems

Users can redeem their shares at any point in time if there is liquidity available.

The amount of a withdraw or redeem can be limited by the strategist by overriding the maxAvailableWithdraw function.

In order to properly comply with the ERC-4626 standard and still allow losses, both withdraw and redeem have an additional optional parameter of 'maxLoss' that can be used. The default for 'maxLoss' is 0 (i.e. revert if any loss) for withdraws, and 10_000 (100%) for redeems.

Strategy Shares

The strategy issues shares to each depositor to track their relative share of assets. Shares are ERC20 transferable yield-bearing tokens.

They are ERC4626 compliant. Please read ERC4626 compliance to understand the implications.

Accounting

The strategy will evaluate profit and losses from the yield generating activities.

This is done comparing the current totalAssets of the strategy with the amount returned from _harvestAndReport()

If totalAssets < newTotalAssets: the vault will record a loss If totalAssets > newTotalAssets: the vault will record a profit

Both loss and profit will impact strategy's totalAssets, increasing if there are profits, decreasing if there are losses.

Fees

Fee assessment and distribution is handled during each report call after profits or losses are recorded.

It will report the amount of fees that need to be charged and the strategy will issue shares for that amount of fees.

There are two potential fees. Performance fees and protocol fees. Performance fees are configurable by management of the strategy and payed based on the reported profit during each report with a min of 5% and a max of 50%.

Protocol fees are configured by Yearn governance through the Factory and are taken as a percent of the performanceFees charged. I.E. profit = 100, performance fees = 20% protocol fees = 10%. Then total fees charged = 100 * .2 = 20 of which 10% is sent to the protocol fee recipient (2) and 90% (18) is sent the strategy specific performanceFeeRecipient.

Profit distribution

Profit from report calls will accumulate in a buffer. This buffer will be linearly unlocked over the locking period seconds at profitUnlockingRate.

Profits will be locked for a max period of time of profitMaxUnlockTime seconds and will be gradually distributed. To avoid spending too much gas for profit unlock, the amount of time a profit will be locked is a weighted average between the new profit and the previous profit.

new_locking_period = current_locked_profit pending_time_to_unlock + new_profit PROFIT_MAX_UNLOCK_TIME / (current_locked_profit + new_profit) new_profit_unlocking_rate = (locked_profit + new_profit) / new_locking_period

Losses will be offset by locked profit, if possible.

The issue of new shares due to fees will also unlock profit so that PPS does not go down.

Both of this offsets will prevent front running (as the profit was already earned and was not distributed yet)

Strategy Management

Strategy management is held by the 'management' address that can be updated by the current 'managment'. Changing 'management' is a two-step process, so first the current management will have to set 'pendingManagement' then that pending management will need to accept the role.

Management has the ability to set all the configurable variables for their specific Strategy.

The base strategy has purposely been written to limit the actual control management has over any important functionality. Meaning they are not capable of stealing any funds from the strategy or otherwise tampering with deposited funds, unless purposefully written in within their specific Strategy.

The configurable variables within managements control are:

Setting Pending Management

This allows the current management to set a new non-zero address to take over as the management of the strategy.

Accepting Management

This allows the current 'pendingManagement' to accept the ownership of the contract.

Setting the keeper

Setting the address that is also allowed to call report and tend functions.

Setting Performance Fee

Setting the percent in terms of basis points for the amount of profit to be charged as a fee.

This has a minimum of 5% and a maximum of 50%.

Setting performance fee recipient

Setting the non-zero address that will receive any shares issued as a result of the performance fee.

Setting the profit unlock period

Sets the time in seconds that controls how fast profits will unlock.

This can be customized based on the strategy. Based on aspects such as TVL, expected returns etc.

ERC4626 compliance

Strategy Shares are ERC4626 compliant.

Emergency Operation

There is default emergency functions built in. First of which is shutdownStrategy. This can only ever be called by the management address and is non-reversible.

Once this is called it will stop any further deposit or mints but will have no effect on any other functionality including withdraw, redeem, report and tend. This is to all the management to continue potentially recording profits or losses and users to withdraw even post shut down.

This can be used in an emergency or simply to retire a vault.

Once a strategy is shutdown management can also call emergencyWithdraw(amount). Which will tell the strategy to withdraw a specified amount from the yield source and keep it as idle in the vault. This function will also do any needed updates to totalDebt and totalIdle, based on amounts withdrawn to assure withdraws continue to function properly.

All other emergency functionality is left up to the individual strategist.

Withdrawals

Withdrawals can't be paused under any circumstance unless built in a specific implementation.

Use

A strategist can simply inherit the BaseTokenizedStrategy.sol contract and override 3 simple functions with their specific needs.

The strategies code has been designed as a non-opinionated system to distribute funds of depositors to a single yield generating opportunity while managing accounting in a robust way.

The depositors receive shares of the strategy representing their relative share that can then be redeemed or used as yield-bearing tokens.

The Strategy does not have a preference on any of the dimensions that should be considered when operating a strategy:

  • Decentralization: management and keeper roles can be handled by EOA's, multi-sigs or any other form of governance.
  • Permissionlessness: The strategies default to be fully permissioned. However, any strategist can easily implement white lists or any other method they desire.
  • Liquidity: The strategy can be fully liquid at any time or only allow withdraws of idle funds, depending on the strategy implementation.
  • Risk: Strategy developers can deploy funds into any opportunity they desire no matter the expected risks or returns.
  • Automation: all the required actions to maintain the vault can be called by bots or manually, depending on periphery implementation

The compromises will come with the specific yield-generating opportunity and parameters used by the strategies management.

This allows different players to deploy their own version and implement their own constraints (or not use any at all)

Example constraints:

  • Illiquid Strategy: A strategy must join AMM pools, which can be sandwiched by permissionless deposits/withdraws. So it only deposits during reports or tend calls from a trusted relay and limits withdraws to the amount of asset currently loose within the contract.
  • Permissioned Version: A strategy decides to only allow a certain address deposit into the vault by overriding maxAvailableDeposit.
  • Risk: A strategist implements an options strategy that can create large positive gains or potentially loose all deposited funds.
  • ...

Development

Strategists should be able to use a pre-built "Strategy Mix" that will contain the imported BaseTokenizedStrategy.sol as well as standardized tests for any 4626 vault. Developing a strategy can be as simple as overriding three functions, with the potential for any number of other constraints or actions to be built on top of it. The Base implementation is only ~2KB, meaning there is plenty of room for strategists to build complex implementations while not having to be concerned with the generic functionality.

Needed to Override

_deployFunds(uint256 _amount): This function is called after every deposit or mint. Its only job is to deposit up to the _amount of asset.

_freeFunds(uint256 _amount): This function is called during every withdrawal or redemption and should attempt to simply withdraw the '_amount' of 'asset'. Any difference between _amount and whats actually withdrawn will be counted as a loss

_harvestandReport(): This function is used during a report and should accrue all rewards and return the total amount of 'asset' the strategy currently has in its control.

Optional to Override

While it can be possible to deploy a completely ERC-4626 compliant vault with just those three functions it does allow for further customization if the strategist desires.

_tend and tendTrigger can be overridden to signal to keepers the need for any sort of maintenance or reward selling between reports.

maxAvailableDeposit(address _owner) can be overridden to implement any type of deposit limit.

maxAvailableWithdraw(address _owner) can be used to limit the amount that a user can withdraw at any given moment.

_emergencyWithdraw(uint256 _amount) can be overridden to provide a manual method for management to pull funds from a yield source in an emergency when the vault is shut down.

Deployment

All strategies deployed will have the address of the deployed 'TokenizedStrategy' set as a constant to be used as the address to forward all external calls to that are not defined in the Strategy.

When deploying a new Strategy, it requires the following parameters:

  • asset: address of the ERC20 token that can be deposited in the strategy
  • name: name of Shares as described in ERC20

All other parameters will default to generic values and can be adjusted post-deployment by the deployer if desired.

Read More