BaseStrategy
Author: yearn.finance
BaseStrategy implements all of the required functionality to
seamlessly integrate with the TokenizedStrategy
implementation contract
allowing anyone to easily build a fully permissionless ERC-4626 compliant
Vault by inheriting this contract and overriding three simple functions.
It utilizes an immutable proxy pattern that allows the BaseStrategy
to remain simple and small. All standard logic is held within the
TokenizedStrategy
and is reused over any n strategies all using the
fallback
function to delegatecall the implementation so that strategists
can only be concerned with writing their strategy specific code.
This contract should be inherited and the three main abstract methods
_deployFunds
, _freeFunds
and _harvestAndReport
implemented to adapt
the Strategy to the particular needs it has to generate yield. There are
other optional methods that can be implemented to further customize
the strategy if desired.
All default storage for the strategy is controlled and updated by the
TokenizedStrategy
. The implementation holds a storage struct that
contains all needed global variables in a manual storage slot. This
means strategists can feel free to implement their own custom storage
variables as they need with no concern of collisions. All global variables
can be viewed within the Strategy by a simple call using the
TokenizedStrategy
variable. IE: TokenizedStrategy.globalVariable();.
State Variables
tokenizedStrategyAddress
This is the address of the TokenizedStrategy implementation contract that will be used by all strategies to handle the accounting, logic, storage etc. Any external calls to the that don't hit one of the functions defined in this base or the strategy will end up being forwarded through the fallback function, which will delegateCall this address. This address should be the same for every strategy, never be adjusted and always be checked before any integration with the Strategy.
address public constant tokenizedStrategyAddress = 0x2e234DAe75C793f67A35089C9d99245E1C58470b;
asset
Underlying asset the Strategy is earning yield on. Stored here for cheap retrievals within the strategy.
ERC20 internal immutable asset;
TokenizedStrategy
This variable is set to address(this) during initialization of each strategy. This can be used to retrieve storage data within the strategy contract as if it were a linked library. i.e. uint256 totalAssets = TokenizedStrategy.totalAssets() Using address(this) will mean any calls using this variable will lead to a call to itself. Which will hit the fallback function and delegateCall that to the actual TokenizedStrategy.
ITokenizedStrategy internal immutable TokenizedStrategy;
Functions
onlySelf
Used on TokenizedStrategy callback functions to make sure it is post a delegateCall from this address to the TokenizedStrategy.
modifier onlySelf();
onlyManagement
Use to assure that the call is coming from the strategies management.
modifier onlyManagement();
onlyKeepers
Use to assure that the call is coming from either the strategies management or the keeper.
modifier onlyKeepers();
onlyEmergencyAuthorized
Use to assure that the call is coming from either the strategies management or the emergency admin.
modifier onlyEmergencyAuthorized();
_onlySelf
Require that the msg.sender is this address.
function _onlySelf() internal view;
constructor
Used to initialize the strategy on deployment.
This will set the TokenizedStrategy
variable for easy
internal view calls to the implementation. As well as
initializing the default storage variables based on the
parameters and using the deployer for the permissioned roles.
constructor(address _asset, string memory _name);
Parameters
Name | Type | Description |
---|---|---|
_asset | address | Address of the underlying asset. |
_name | string | Name the strategy will use. |
_deployFunds
Can deploy up to '_amount' of 'asset' in the yield source.
This function is called at the end of a deposit
or mint
call. Meaning that unless a whitelist is implemented it will
be entirely permissionless and thus can be sandwiched or otherwise
manipulated.
function _deployFunds(uint256 _amount) internal virtual;
Parameters
Name | Type | Description |
---|---|---|
_amount | uint256 | The amount of 'asset' that the strategy can attempt to deposit in the yield source. |
_freeFunds
Should attempt to free the '_amount' of 'asset'.
NOTE: The amount of 'asset' that is already loose has already
been accounted for.
This function is called during withdraw
and redeem
calls.
Meaning that unless a whitelist is implemented it will be
entirely permissionless and thus can be sandwiched or otherwise
manipulated.
Should not rely on asset.balanceOf(address(this)) calls other than
for diff accounting purposes.
Any difference between _amount
and what is actually freed will be
counted as a loss and passed on to the withdrawer. This means
care should be taken in times of illiquidity. It may be better to revert
if withdraws are simply illiquid so not to realize incorrect losses.
function _freeFunds(uint256 _amount) internal virtual;
Parameters
Name | Type | Description |
---|---|---|
_amount | uint256 |
_harvestAndReport
Internal function to harvest all rewards, redeploy any idle
funds and return an accurate accounting of all funds currently
held by the Strategy.
This should do any needed harvesting, rewards selling, accrual,
redepositing etc. to get the most accurate view of current assets.
NOTE: All applicable assets including loose assets should be
accounted for in this function.
Care should be taken when relying on oracles or swap values rather
than actual amounts as all Strategy profit/loss accounting will
be done based on this returned value.
This can still be called post a shutdown, a strategist can check
TokenizedStrategy.isShutdown()
to decide if funds should be
redeployed or simply realize any profits/losses.
function _harvestAndReport() internal virtual returns (uint256 _totalAssets);
Returns
Name | Type | Description |
---|---|---|
_totalAssets | uint256 | A trusted and accurate account for the total amount of 'asset' the strategy currently holds including idle funds. |
_tend
Optional function for strategist to override that can be called in between reports. If '_tend' is used tendTrigger() will also need to be overridden. This call can only be called by a permissioned role so may be through protected relays. This can be used to harvest and compound rewards, deposit idle funds, perform needed position maintenance or anything else that doesn't need a full report for. EX: A strategy that can not deposit funds without getting sandwiched can use the tend when a certain threshold of idle to totalAssets has been reached. This will have no effect on PPS of the strategy till report() is called.
function _tend(uint256 _totalIdle) internal virtual;
Parameters
Name | Type | Description |
---|---|---|
_totalIdle | uint256 | The current amount of idle funds that are available to deploy. |
_tendTrigger
Optional trigger to override if tend() will be used by the strategy. This must be implemented if the strategy hopes to invoke _tend().
function _tendTrigger() internal view virtual returns (bool);
Returns
Name | Type | Description |
---|---|---|
<none> | bool | . Should return true if tend() should be called by keeper or false if not. |
tendTrigger
Returns if tend() should be called by a keeper.
function tendTrigger() external view virtual returns (bool, bytes memory);
Returns
Name | Type | Description |
---|---|---|
<none> | bool | . Should return true if tend() should be called by keeper or false if not. |
<none> | bytes | . Calldata for the tend call. |
availableDepositLimit
Gets the max amount of asset
that an address can deposit.
Defaults to an unlimited amount for any address. But can
be overridden by strategists.
This function will be called before any deposit or mints to enforce
any limits desired by the strategist. This can be used for either a
traditional deposit limit or for implementing a whitelist etc.
EX:
if(isAllowed[_owner]) return super.availableDepositLimit(_owner);
This does not need to take into account any conversion rates
from shares to assets. But should know that any non max uint256
amounts may be converted to shares. So it is recommended to keep
custom amounts low enough as not to cause overflow when multiplied
by totalSupply
.
function availableDepositLimit(address) public view virtual returns (uint256);
Parameters
Name | Type | Description |
---|---|---|
<none> | address |
Returns
Name | Type | Description |
---|---|---|
<none> | uint256 | . The available amount the _owner can deposit in terms of asset |
availableWithdrawLimit
Gets the max amount of asset
that can be withdrawn.
Defaults to an unlimited amount for any address. But can
be overridden by strategists.
This function will be called before any withdraw or redeem to enforce
any limits desired by the strategist. This can be used for illiquid
or sandwichable strategies. It should never be lower than totalIdle
.
EX:
return TokenIzedStrategy.totalIdle();
This does not need to take into account the _owner
's share balance
or conversion rates from shares to assets.
function availableWithdrawLimit(address) public view virtual returns (uint256);
Parameters
Name | Type | Description |
---|---|---|
<none> | address |
Returns
Name | Type | Description |
---|---|---|
<none> | uint256 | . The available amount that can be withdrawn in terms of asset |
_emergencyWithdraw
*Optional function for a strategist to override that will
allow management to manually withdraw deployed funds from the
yield source if a strategy is shutdown.
This should attempt to free _amount
, noting that _amount
may
be more than is currently deployed.
NOTE: This will not realize any profits or losses. A separate
report
will be needed in order to record any profit/loss. If
a report may need to be called after a shutdown it is important
to check if the strategy is shutdown during _harvestAndReport
so that it does not simply re-deploy all funds that had been freed.
if(freeAsset > 0 && !TokenizedStrategy.isShutdown()) {
depositFunds...
}
function _emergencyWithdraw(uint256 _amount) internal virtual;
Parameters
Name | Type | Description |
---|---|---|
_amount | uint256 | The amount of asset to attempt to free. |
deployFunds
Can deploy up to '_amount' of 'asset' in yield source.
Callback for the TokenizedStrategy to call during a deposit
or mint
to tell the strategy it can deploy funds.
Since this can only be called after a deposit
or mint
delegateCall to the TokenizedStrategy msg.sender == address(this).
Unless a whitelist is implemented this will be entirely permissionless
and thus can be sandwiched or otherwise manipulated.
function deployFunds(uint256 _amount) external virtual onlySelf;
Parameters
Name | Type | Description |
---|---|---|
_amount | uint256 | The amount of 'asset' that the strategy can attempt to deposit in the yield source. |
freeFunds
Should attempt to free the '_amount' of 'asset'.
Callback for the TokenizedStrategy to call during a withdraw or redeem to free the needed funds to service the withdraw. This can only be called after a 'withdraw' or 'redeem' delegateCall to the TokenizedStrategy so msg.sender == address(this).
function freeFunds(uint256 _amount) external virtual onlySelf;
Parameters
Name | Type | Description |
---|---|---|
_amount | uint256 | The amount of 'asset' that the strategy should attempt to free up. |
harvestAndReport
Returns the accurate amount of all funds currently held by the Strategy.
Callback for the TokenizedStrategy to call during a report to get an accurate accounting of assets the strategy controls. This can only be called after a report() delegateCall to the TokenizedStrategy so msg.sender == address(this).
function harvestAndReport() external virtual onlySelf returns (uint256);
Returns
Name | Type | Description |
---|---|---|
<none> | uint256 | . A trusted and accurate account for the total amount of 'asset' the strategy currently holds including idle funds. |
tendThis
Will call the internal '_tend' when a keeper tends the strategy.
Callback for the TokenizedStrategy to initiate a _tend call in the strategy.
This can only be called after a tend() delegateCall to the TokenizedStrategy
so msg.sender == address(this).
We name the function tendThis
so that tend
calls are forwarded to
the TokenizedStrategy.
function tendThis(uint256 _totalIdle) external virtual onlySelf;
Parameters
Name | Type | Description |
---|---|---|
_totalIdle | uint256 | The amount of current idle funds that can be deployed during the tend |
shutdownWithdraw
Will call the internal '_emergencyWithdraw' function.
Callback for the TokenizedStrategy during an emergency withdraw.
This can only be called after a emergencyWithdraw() delegateCall to
the TokenizedStrategy so msg.sender == address(this).
We name the function shutdownWithdraw
so that emergencyWithdraw
calls are forwarded to the TokenizedStrategy.
function shutdownWithdraw(uint256 _amount) external virtual onlySelf;
Parameters
Name | Type | Description |
---|---|---|
_amount | uint256 | The amount of asset to attempt to free. |
_delegateCall
Function used to delegate call the TokenizedStrategy with
certain _calldata
and return any return values.
This is used to setup the initial storage of the strategy, and
can be used by strategist to forward any other call to the
TokenizedStrategy implementation.
function _delegateCall(bytes memory _calldata) internal returns (bytes memory);
Parameters
Name | Type | Description |
---|---|---|
_calldata | bytes | The abi encoded calldata to use in delegatecall. |
Returns
Name | Type | Description |
---|---|---|
<none> | bytes | . The return value if the call was successful in bytes. |
fallback
Execute a function on the TokenizedStrategy and return any value. This fallback function will be executed when any of the standard functions defined in the TokenizedStrategy are called since they wont be defined in this contract. It will delegatecall the TokenizedStrategy implementation with the exact calldata and return any relevant values.
fallback() external;