BaseStrategy
Author: Golem Foundation
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();.
Note: security-contact: [email protected]
State Variables
TOKENIZED_STRATEGY_ADDRESS
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 immutable TOKENIZED_STRATEGY_ADDRESS;
asset
Underlying ERC20 asset the strategy earns yield on
Immutable, set during construction. Stored here for gas-efficient access Strategies deposit this asset into yield sources
ERC20 internal immutable asset;
TokenizedStrategy
Internal interface to TokenizedStrategy implementation
*Set to address(this) during initialization USAGE: Any call to TokenizedStrategy.xxx() will:
- Call address(this).xxx()
- Hit the fallback function
- Delegatecall to TOKENIZED_STRATEGY_ADDRESS This pattern allows strategies to access TokenizedStrategy storage as if it were a linked library*
ITokenizedStrategy internal immutable TokenizedStrategy;
Functions
onlySelf
Ensures function is called via delegatecall from this contract
Used on TokenizedStrategy callbacks to verify delegatecall context Prevents external calls to internal hook functions
modifier onlySelf();
onlyManagement
Restricts function to management address only
Calls TokenizedStrategy.requireManagement to validate
modifier onlyManagement();
onlyKeepers
Restricts function to keeper or management
Calls TokenizedStrategy.requireKeeperOrManagement to validate Used for report() and tend() operations
modifier onlyKeepers();
onlyEmergencyAuthorized
Restricts function to emergencyAdmin or management
Calls TokenizedStrategy.requireEmergencyAuthorized to validate Used for emergency shutdown and withdrawal operations
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,
    address _management,
    address _keeper,
    address _emergencyAdmin,
    address _donationAddress,
    bool _enableBurning,
    address _tokenizedStrategyAddress
);
Parameters
| Name | Type | Description | 
|---|---|---|
| _asset | address | Address of the underlying asset. | 
| _name | string | Name the strategy will use. | 
| _management | address | Address with management permissions | 
| _keeper | address | Address with keeper permissions | 
| _emergencyAdmin | address | Address with emergency admin permissions | 
| _donationAddress | address | Address that will receive donations for this specific strategy | 
| _enableBurning | bool | Whether to enable burning shares from dragon router during loss protection | 
| _tokenizedStrategyAddress | address | Address of the TokenizedStrategy implementation | 
_deployFunds
REQUIRED: Deploys assets into the yield source
*Called automatically after deposits/mints to put idle assets to work WHEN CALLED:
- After deposit() or mint() completes
- Via deployFunds() hook from TokenizedStrategy
- msg.sender == address(this) (via delegatecall) IMPLEMENTATION GUIDANCE:
- Transfer _amount of asset from this contract to yield source
- Receive yield-bearing tokens (e.g., aTokens, LP tokens)
- Store any necessary state for tracking
- Consider gas costs - don't deploy dust amounts SECURITY CONSIDERATIONS:
- PERMISSIONLESS: Can be called by anyone via deposit
- MEV RISK: May be sandwichable - consider slippage protection
- VALIDATION: Verify yield source addresses/parameters
- FAILURE: Revert on critical errors, idle funds stay in strategy*
function _deployFunds(uint256 _amount) internal virtual;
Parameters
| Name | Type | Description | 
|---|---|---|
| _amount | uint256 | Amount of asset to deploy | 
_freeFunds
REQUIRED: Frees assets from the yield source for withdrawal
*Called during withdraw/redeem to liquidate positions and return assets WHEN CALLED:
- During withdraw() or redeem() operations
- Via freeFunds() hook from TokenizedStrategy
- msg.sender == address(this) (via delegatecall)
- Idle assets already accounted for (only called if needed) IMPLEMENTATION GUIDANCE:
- Withdraw _amount from yield source to this contract
- DO NOT rely on asset.balanceOf(this) except for diff accounting
- Any shortfall is counted as loss and passed to withdrawer
- Consider reverting if illiquid rather than realizing losses LOSS HANDLING:
- Freed amount < _amount: Shortfall = realized loss for user
- Loss must be within maxLoss tolerance or withdrawal reverts
- CAREFUL: Temporary illiquidity ≠ permanent loss SECURITY CONSIDERATIONS:
- PERMISSIONLESS: Can be called by anyone via withdraw
- MEV RISK: May be sandwichable - consider slippage protection
- ILLIQUIDITY: Revert if withdrawal would realize unfair losses
- PRECISION: Track actual amounts freed, not estimates*
function _freeFunds(uint256 _amount) internal virtual;
Parameters
| Name | Type | Description | 
|---|---|---|
| _amount | uint256 | Amount of asset needed | 
_harvestAndReport
REQUIRED: Harvests rewards and reports accurate asset accounting
*Called by report() to update strategy's total assets and realize profits/losses WHEN CALLED:
- Via report() by keeper or management
- Via harvestAndReport() hook from TokenizedStrategy
- Can be called even after shutdown CRITICAL RESPONSIBILITIES:
- Harvest all pending rewards from yield source
- Sell/swap rewards to base asset (if applicable)
- Compound rewards back into position (if desired)
- Account for ALL assets: deployed + idle + pending rewards
- Return accurate total (profit/loss calculated from this) ACCOUNTING RULES:
- Return value MUST include: deposited assets + idle assets + accrued rewards
- Return value determines profit/loss since last report
- Profit = (currentTotal - lastReportedTotal) > 0
- Loss = (currentTotal - lastReportedTotal) < 0
- BE PRECISE: All PnL accounting depends on this number POST-SHUTDOWN BEHAVIOR:
- Check TokenizedStrategy.isShutdown()
- If shutdown: Don't redeploy, just harvest and account
- Allow final report to realize remaining positions SECURITY CONSIDERATIONS:
- ORACLE RISK: Prefer actual balances over oracle values
- MANIPULATION: Ensure returned value reflects real assets
- PRECISION: Rounding errors accumulate - be conservative
- TIMING: MEV risk when harvesting - use protected mempools*
Note: security: Return value determines profit/loss - must be manipulation-resistant
function _harvestAndReport() internal virtual returns (uint256 _totalAssets);
Returns
| Name | Type | Description | 
|---|---|---|
| _totalAssets | uint256 | CRITICAL: Accurate total of all strategy assets (deployed + idle) | 
_tend
OPTIONAL: Performs maintenance between reports without updating PPS
*Called by tend() between reports for position maintenance USE CASES:
- Harvest and compound rewards without full report
- Deploy idle funds when threshold reached
- Rebalance positions
- Update protocol-specific parameters WHEN TO USE:
- Strategy has idle funds but depositing is MEV-risky
- Rewards need compounding more frequently than reports
- Position requires periodic maintenance IMPORTANT:
- Does NOT update strategy PPS (no profit/loss recorded)
- Only affects internal positions
- Must also override _tendTrigger() to activate*
function _tend(uint256 _totalIdle) internal virtual;
Parameters
| Name | Type | Description | 
|---|---|---|
| _totalIdle | uint256 | Current idle funds available to deploy | 
_tendTrigger
OPTIONAL: Determines if tend() should be called
MUST be overridden if _tend() is implemented
function _tendTrigger() internal view virtual returns (bool);
Returns
| Name | Type | Description | 
|---|---|---|
| <none> | bool | shouldTend True if tend() should be called by keeper | 
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 | shouldTend True if tend() should be called by a keeper. | 
| <none> | bytes | tendCalldata 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 an allowset etc.
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.
The address that is depositing into the strategy can be used by overrides to enforce custom limits.
function availableDepositLimit(address) public view virtual returns (uint256);
Returns
| Name | Type | Description | 
|---|---|---|
| <none> | uint256 | Available amount owner can deposit | 
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.
This does not need to take into account the _owner's share balance
or conversion rates from shares to assets.
The address that is withdrawing from the strategy can be used by overrides to enforce custom limits.
function availableWithdrawLimit(address) public view virtual returns (uint256);
Returns
| Name | Type | Description | 
|---|---|---|
| <none> | uint256 | Available amount that can be withdrawn | 
_emergencyWithdraw
OPTIONAL: Manually withdraws funds after shutdown
*Allows management to recover funds from yield source post-shutdown WHEN CALLED:
- Only after strategy is shutdown
- Via emergencyWithdraw() by management or emergencyAdmin IMPORTANT:
- Does NOT realize profit/loss (need separate report() for that)
- _amount may exceed currently deployed amount
- In _harvestAndReport(), check isShutdown() to avoid redeploying*
function _emergencyWithdraw(uint256 _amount) internal virtual;
Parameters
| Name | Type | Description | 
|---|---|---|
| _amount | uint256 | 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 an allowset 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 | Amount of asset strategy can deploy | 
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 | Amount of asset 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 | Amount of current idle funds available to 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 | 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 returndata);
Parameters
| Name | Type | Description | 
|---|---|---|
| _calldata | bytes | ABI encoded calldata to use in delegatecall | 
Returns
| Name | Type | Description | 
|---|---|---|
| returndata | bytes | Return value if 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;
Errors
NotSelf
error NotSelf();