Skip to main content

BaseStrategy.sol

Git Source

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

NameTypeDescription
_assetaddressAddress of the underlying asset.
_namestringName 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

NameTypeDescription
_amountuint256The 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

NameTypeDescription
_amountuint256

_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

NameTypeDescription
_totalAssetsuint256A 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

NameTypeDescription
_totalIdleuint256The 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

NameTypeDescription
<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

NameTypeDescription
<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

NameTypeDescription
<none>address

Returns

NameTypeDescription
<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

NameTypeDescription
<none>address

Returns

NameTypeDescription
<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.

Example
if(freeAsset > 0 && !TokenizedStrategy.isShutdown()) {
depositFunds...
}
function _emergencyWithdraw(uint256 _amount) internal virtual;

Parameters

NameTypeDescription
_amountuint256The 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

NameTypeDescription
_amountuint256The 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

NameTypeDescription
_amountuint256The 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

NameTypeDescription
<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

NameTypeDescription
_totalIdleuint256The 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

NameTypeDescription
_amountuint256The 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

NameTypeDescription
_calldatabytesThe abi encoded calldata to use in delegatecall.

Returns

NameTypeDescription
<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;