TokenizedStrategy
Author: yearn.finance; forked and modified by Golem Foundation
This TokenizedStrategy is a fork of Yearn's TokenizedStrategy that has been
modified by Octant to support donation functionality and other security enhancements.
The original contract can be used by anyone wishing to easily build
and deploy their own custom ERC4626 compliant single strategy Vault.
The TokenizedStrategy contract is meant to be used as the proxy
implementation contract that will handle all logic, storage and
management for a custom strategy that inherits the BaseStrategy.
Any function calls to the strategy that are not defined within that
strategy will be forwarded through a delegateCall to this contract.
A strategist only needs to override a few simple functions that are
focused entirely on the strategy specific needs to easily and cheaply
deploy their own permissionless 4626 compliant vault.
*Changes from Yearn V3:
- Added dragonRouter to the StrategyData struct to enable yield distribution
- Added getter and setter for dragonRouter
- Added validation checks for all critical addresses (management, keeper, emergencyAdmin, dragonRouter)
- Enhanced initialize function to include emergencyAdmin and dragonRouter parameters
- Standardized error messages for zero-address checks
- Removed the yield/profit unlocking mechanism (profits are immediately realized)
- Made the report() function virtual to enable specialized implementations
- Made this contract abstract as a base for specialized strategy implementations Two specialized implementations are provided:
- YieldDonatingTokenizedStrategy: Mints profits as new shares and sends them to a specified dragon router
- YieldSkimmingTokenizedStrategy: Skims the appreciation of asset and dilutes the original shares by minting new ones to the dragon router Trust Minimization (design goals):
- No protocol performance/management fees at the strategy level; yield flows directly to the configured donation destination
- Dragon router changes are subject to a mandatory cooldown (see setDragonRouter/finalizeDragonRouterChange)
- Clear role separation: management, keeper, emergencyAdmin; keepers focus on report/tend cadence Security Model (trusted roles and expectations):
- Management: updates roles, initiates dragon router changes, may shutdown in emergencies
- Keeper: calls report/tend at appropriate intervals; use MEV-protected mempools when possible
- Emergency Admin: can shutdown and perform emergency withdrawals Threat Model Boundaries (non-exhaustive):
- In scope: precision/rounding issues, price-per-share manipulation via airdrops (mitigated by tracked totalAssets), reentrancy (guarded), misuse of roles
- Out of scope: malicious management/keeper/emergency admin; complete compromise of external yield sources Functional Requirements mapping (high-level):
- FR-1 Initialization: initialize() parameters include asset, name and roles, plus donation routing settings
- FR-2 Asset management: BaseStrategy overrides (_deployFunds/_freeFunds/_harvestAndReport) power the yield logic
- FR-3 Roles: requireManagement/requireKeeperOrManagement/requireEmergencyAuthorized helpers enforce permissions
- FR-4 Donation management: dragon router cooldown and two-step change via setDragonRouter/finalize/cancel
- FR-5 Emergency: shutdownStrategy/emergencyWithdraw hooks in specialized implementations
- FR-6 ERC-4626: full ERC-4626 surface for deposits/withdrawals and previews is implemented WARNING: When creating custom strategies, DO NOT declare state variables outside the StrategyData struct. Doing so risks storage collisions if the implementation contract changes. Either extend the StrategyData struct or use a custom storage slot.*
Notes:
-
security-contact: [email protected]
-
origin: https://github.com/yearn/tokenized-strategy/blob/master/src/TokenizedStrategy.sol
State Variables
API_VERSION
API version identifier for this TokenizedStrategy implementation
Used for tracking strategy versions and compatibility
string internal constant API_VERSION = "1.0.0";
ENTERED
Reentrancy guard flag value during function execution
Set to 2 when a protected function is executing
uint8 internal constant ENTERED = 2;
NOT_ENTERED
Reentrancy guard flag value when not executing
Set to 1 when no protected function is executing (default state)
uint8 internal constant NOT_ENTERED = 1;
MAX_BPS
Maximum basis points (100%)
Used for percentage calculations (loss tolerance, fees, etc.) 10,000 basis points = 100%
uint256 internal constant MAX_BPS = 10_000;
DRAGON_ROUTER_COOLDOWN
Mandatory cooldown period for dragon router changes
14 days in seconds. Prevents rapid changes that could enable attacks OCTANT-SPECIFIC security feature to protect yield distribution
uint256 internal constant DRAGON_ROUTER_COOLDOWN = 14 days;
PERMIT_TYPEHASH
EIP-2612 Permit type hash for gasless approvals
Used to validate permit signatures
bytes32 internal constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
EIP712DOMAIN_TYPEHASH
EIP-712 domain type hash for structured data signing
Used in DOMAIN_SEPARATOR calculation
bytes32 internal constant EIP712DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
NAME_HASH
Precomputed hash of strategy name for EIP-712 domain
"Octant Vault" - saves gas by computing once
bytes32 internal constant NAME_HASH = keccak256("Octant Vault");
VERSION_HASH
Precomputed hash of API version for EIP-712 domain
Saves gas by computing once
bytes32 internal constant VERSION_HASH = keccak256(bytes(API_VERSION));
BASE_STRATEGY_STORAGE
Custom storage slot for StrategyData struct
*CRITICAL: This custom slot prevents storage collisions in the proxy pattern STORAGE PATTERN:
- Strategy contracts delegatecall to this implementation
- Delegatecall executes in the context of the calling contract's storage
- Without a custom slot, storage variables would collide
- This slot is deterministically generated from "octant.base.strategy.storage" CALCULATION (ERC-7201): keccak256(abi.encode(uint256(keccak256("octant.base.strategy.storage")) - 1)) & ~bytes32(uint256(0xff)) See: https://eips.ethereum.org/EIPS/eip-7201 SAFETY: Strategists can use any storage in their strategy contract without worrying about colliding with TokenizedStrategy's storage*
bytes32 internal constant BASE_STRATEGY_STORAGE =
keccak256(abi.encode(uint256(keccak256("octant.base.strategy.storage")) - 1)) & ~bytes32(uint256(0xff));
Functions
constructor
Constructor prevents direct initialization of implementation contract
Sets asset to address(1) to make this contract unusable as a standalone strategy This contract is meant to be used ONLY as an implementation for proxy contracts Each proxy gets its own storage via the custom BASE_STRATEGY_STORAGE slot
constructor();
onlyManagement
Restricts function access to management address only
Reverts with "!management" if caller is not management
modifier onlyManagement();
onlyKeepers
Restricts function access to keeper or management
Reverts with "!keeper" if caller is neither keeper nor management Used for report() and tend() functions
modifier onlyKeepers();
onlyEmergencyAuthorized
Restricts function access to emergencyAdmin or management
Reverts with "!emergency authorized" if caller is neither Used for emergency shutdown and withdrawal functions
modifier onlyEmergencyAuthorized();
nonReentrant
Prevents reentrancy on state-changing functions
Uses entered flag (ENTERED=2, NOT_ENTERED=1) instead of bool for gas optimization Applied to all deposit, withdraw, and reporting functions Reverts with "ReentrancyGuard: reentrant call" if reentrant call detected
modifier nonReentrant();
requireManagement
Validates that an address is the management address
Public so it can be used by both modifiers and strategy implementations When called from strategy via delegatecall, msg.sender is the strategy address, so we pass the actual sender as a parameter
Note: security: Reverts with "!management" if sender is not management
function requireManagement(address _sender) public view;
Parameters
| Name | Type | Description |
|---|---|---|
_sender | address | Address to validate (typically msg.sender) |
requireKeeperOrManagement
Validates that an address is either keeper or management
Public so it can be used by both modifiers and strategy implementations Used to gate report() and tend() functions
Note: security: Reverts with "!keeper" if sender is neither keeper nor management
function requireKeeperOrManagement(address _sender) public view;
Parameters
| Name | Type | Description |
|---|---|---|
_sender | address | Address to validate (typically msg.sender) |
requireEmergencyAuthorized
Validates that an address is either emergencyAdmin or management
Public so it can be used by both modifiers and strategy implementations Used to gate emergency shutdown and withdrawal functions
Note: security: Reverts with "!emergency authorized" if sender is neither
function requireEmergencyAuthorized(address _sender) public view;
Parameters
| Name | Type | Description |
|---|---|---|
_sender | address | Address to validate (typically msg.sender) |
_strategyStorage
Returns a storage pointer to the StrategyData struct
function _strategyStorage() internal pure returns (StrategyData storage S);
Returns
| Name | Type | Description |
|---|---|---|
S | StrategyData | Storage reference to the strategy's data at the custom slot GAS OPTIMIZATION: - Only loads the storage slot pointer, not the actual struct contents - Struct fields are loaded lazily when accessed - Multiple calls in same function reuse same storage pointer ASSEMBLY USAGE: - Required because Solidity doesn't support direct storage slot assignment - Loads BASE_STRATEGY_STORAGE into S.slot - Safe because we're just setting a storage location |
initialize
Initializes a new strategy with all required parameters
*CRITICAL: Can only be called ONCE per strategy (checked via asset == address(0)) Should be called atomically right after strategy deployment INITIALIZATION SEQUENCE:
- Validates not already initialized
- Sets asset and derives decimals
- Sets strategy name
- Initializes lastReport to current timestamp
- Sets all role addresses (with zero-address validation)
- Configures burning mechanism
- Emits NewTokenizedStrategy event for indexers OCTANT CHANGES FROM YEARN:
- Added emergencyAdmin parameter for enhanced security
- Added dragonRouter parameter for yield distribution
- Added enableBurning parameter for loss protection configuration
- Enhanced zero-address validation for all critical addresses POST-INITIALIZATION: All parameters can be updated via management functions except:
- asset (immutable)
- name (can be updated via management)
- symbol (can be updated via management)
- decimals (immutable, derived from asset)*
Notes:
-
security: Can only be called once - no re-initialization possible
-
security: All addresses validated as non-zero
function initialize(
address _asset,
string memory _name,
address _management,
address _keeper,
address _emergencyAdmin,
address _dragonRouter,
bool _enableBurning
) public virtual;
Parameters
| Name | Type | Description |
|---|---|---|
_asset | address | Address of the underlying ERC20 asset (cannot be zero) |
_name | string | Human-readable name for strategy shares (e.g., "Octant Lido ETH Strategy") |
_management | address | Address for primary admin (cannot be zero) |
_keeper | address | Address authorized to call report/tend (cannot be zero) |
_emergencyAdmin | address | Address authorized for emergency actions (cannot be zero) |
_dragonRouter | address | Address to receive minted profit shares (cannot be zero, OCTANT-specific) |
_enableBurning | bool | Whether to burn dragon shares during losses (OCTANT-specific) |
deposit
Mints proportional shares to receiver according to how the strategy calculates the assets to shares conversion
ERC4626-compliant deposit function with reentrancy protection
Note: security: Reentrancy protected
function deposit(uint256 assets, address receiver) external virtual nonReentrant returns (uint256 shares);
Parameters
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets to deposit (or type(uint256).max for full balance) |
receiver | address | Address to receive the minted shares |
Returns
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares minted to receiver |
mint
Mints exact shares by depositing calculated asset amount
*ERC4626-compliant mint function with reentrancy protection CHECKS:
- Shares <= maxMint (also checks if strategy shutdown)
- Assets != 0 (prevents rounding to zero)*
Note: security: Reentrancy protected
function mint(uint256 shares, address receiver) external virtual nonReentrant returns (uint256 assets);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Exact amount of shares to mint |
receiver | address | Address to receive the minted shares |
Returns
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets deposited from caller |
withdraw
Withdraws assets by burning owner's shares (no loss tolerance)
Convenience wrapper that defaults to maxLoss = 0 (no loss accepted) Calls the overloaded withdraw with maxLoss = 0
function withdraw(uint256 assets, address receiver, address owner) external virtual returns (uint256 shares);
Parameters
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets to withdraw |
receiver | address | Address to receive the withdrawn assets |
owner | address | Address whose shares will be burned |
Returns
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares burned from owner |
withdraw
Withdraws assets by burning owner's shares with loss tolerance
ERC4626-extended withdraw with loss parameter and reentrancy protection
Note: security: Reentrancy protected
function withdraw(uint256 assets, address receiver, address owner, uint256 maxLoss)
public
virtual
nonReentrant
returns (uint256 shares);
Parameters
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets to withdraw |
receiver | address | Address to receive the withdrawn assets |
owner | address | Address whose shares are burned |
maxLoss | uint256 | Maximum acceptable loss in basis points (0-10000, where 10000 = 100%) |
Returns
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares burned from owner |
redeem
Redeems shares for assets (accepts any loss)
Convenience wrapper that defaults to maxLoss = MAX_BPS (100%, accepts any loss) Calls the overloaded redeem with maxLoss = MAX_BPS
function redeem(uint256 shares, address receiver, address owner) external virtual returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares to burn |
receiver | address | Address to receive the withdrawn assets |
owner | address | Address whose shares are burned |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | assets Actual amount of assets withdrawn (may be less than expected if loss occurs) |
redeem
Redeems exactly specified shares for assets with loss tolerance
ERC4626-extended redeem with loss parameter and reentrancy protection
Note: security: Reentrancy protected
function redeem(uint256 shares, address receiver, address owner, uint256 maxLoss)
public
virtual
nonReentrant
returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares to burn |
receiver | address | Address to receive the withdrawn assets |
owner | address | Address whose shares are burned |
maxLoss | uint256 | Maximum acceptable loss in basis points (0-10000, where 10000 = 100%) |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | assets Actual amount of assets withdrawn |
totalAssets
Returns total assets under management
CRITICAL: Manually tracked to prevent PPS manipulation via direct transfers Updated during deposits, withdrawals, and report() calls
function totalAssets() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | totalAssets_ Total assets (typically 18 decimals) |
totalSupply
Returns total supply of strategy shares
Includes shares held by all users AND dragon router
function totalSupply() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | totalSupply_ Total shares (typically 18 decimals) |
convertToShares
Converts asset amount to equivalent shares
Uses Floor rounding (conservative for conversions) Formula: (assets * totalSupply) / totalAssets
function convertToShares(uint256 assets) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets to convert |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | shares_ Equivalent amount of shares |
convertToAssets
Converts share amount to equivalent assets
Uses Floor rounding (conservative for conversions) Formula: (shares * totalAssets) / totalSupply
function convertToAssets(uint256 shares) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares to convert |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | assets_ Equivalent amount of assets |
previewDeposit
Previews shares that would be minted for a deposit
Uses Floor rounding
function previewDeposit(uint256 assets) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets to deposit |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | shares_ Expected shares to be minted |
previewMint
Previews assets required to mint exact shares
Uses Ceil rounding
function previewMint(uint256 shares) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares to mint |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | assets_ Required assets for mint |
previewWithdraw
Previews shares that would be burned for a withdrawal
Uses Ceil rounding
function previewWithdraw(uint256 assets) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
assets | uint256 | Amount of assets to withdraw |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | shares_ Expected shares to be burned |
previewRedeem
Previews assets that would be returned for redeeming shares
Uses Floor rounding
function previewRedeem(uint256 shares) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
shares | uint256 | Amount of shares to redeem |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | assets_ Expected assets to be returned |
maxDeposit
Returns maximum assets that can be deposited
Returns 0 if strategy is shutdown Returns type(uint256).max if not shutdown (no hard cap)
function maxDeposit(address receiver) public view virtual returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
receiver | address | Address that would receive the shares |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | max Maximum deposit amount |
maxMint
Total number of shares that can be minted to receiver
of a mint call.
function maxMint(address receiver) public view virtual returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
receiver | address | Address that would receive the shares |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | _maxMint Maximum shares that can be minted |
maxWithdraw
Maximum underlying assets that can be withdrawn by owner.
function maxWithdraw(address owner) public view virtual returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | Address that owns the shares |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | _maxWithdraw Maximum assets that can be withdrawn |
maxWithdraw
Variable maxLoss is ignored.
Accepts a maxLoss variable in order to match the multi
strategy vaults ABI.
function maxWithdraw(address owner, uint256) external view returns (uint256);
maxRedeem
Maximum number of shares that can be redeemed by owner.
function maxRedeem(address owner) public view virtual returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | Address that owns the shares |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | _maxRedeem Maximum shares that can be redeemed |
maxRedeem
Variable maxLoss is ignored.
Accepts a maxLoss variable in order to match the multi
strategy vaults ABI.
function maxRedeem(address owner, uint256) external view returns (uint256);
_totalAssets
Internal implementation of totalAssets.
function _totalAssets(StrategyData storage S) internal view returns (uint256);
_totalSupply
Internal implementation of totalSupply.
function _totalSupply(StrategyData storage S) internal view returns (uint256);
_convertToShares
Internal implementation of convertToShares.
function _convertToShares(StrategyData storage S, uint256 assets, Math.Rounding _rounding)
internal
view
virtual
returns (uint256);
_convertToAssets
Internal implementation of convertToAssets.
function _convertToAssets(StrategyData storage S, uint256 shares, Math.Rounding _rounding)
internal
view
virtual
returns (uint256);
_maxDeposit
Internal implementation of maxDeposit.
function _maxDeposit(StrategyData storage S, address receiver) internal view returns (uint256);
_maxMint
Internal implementation of maxMint.
function _maxMint(StrategyData storage S, address receiver) internal view returns (uint256 maxMint_);
_maxWithdraw
Internal implementation of maxWithdraw.
function _maxWithdraw(StrategyData storage S, address owner) internal view returns (uint256 maxWithdraw_);
_maxRedeem
Internal implementation of maxRedeem.
function _maxRedeem(StrategyData storage S, address owner) internal view returns (uint256 maxRedeem_);
_deposit
Function to be called during deposit and {mint}. This function handles all logic including transfers, minting and accounting. We do all external calls before updating any internal values to prevent view reentrancy issues from the token transfers or the _deployFunds() calls.
function _deposit(StrategyData storage S, address receiver, uint256 assets, uint256 shares) internal virtual;
_withdraw
To be called during redeem and {withdraw}. This will handle all logic, transfers and accounting in order to service the withdraw request. If we are not able to withdraw the full amount needed, it will be counted as a loss and passed on to the user.
function _withdraw(
StrategyData storage S,
address receiver,
address owner,
uint256 assets,
uint256 shares,
uint256 maxLoss
) internal returns (uint256);
report
Function for keepers to call to harvest and record all donations accrued.
*This will account for any gains/losses since the last report. This function is virtual and meant to be overridden by specialized strategies that implement custom yield handling mechanisms. Two primary implementations are provided in specialized strategies:
- YieldDonatingTokenizedStrategy: Mints shares from profits to the dragonRouter
- YieldSkimmingTokenizedStrategy: Skims asset appreciation by diluting shares*
function report() external virtual returns (uint256 profit, uint256 loss);
Returns
| Name | Type | Description |
|---|---|---|
profit | uint256 | Notional amount of gain since last report report in terms of asset. |
loss | uint256 | Notional amount of loss since last report report in terms of asset. |
tend
For a 'keeper' to 'tend' the strategy if a custom tendTrigger() is implemented.
Both 'tendTrigger' and '_tend' will need to be overridden for this to be used. This will callback the internal '_tend' call in the BaseStrategy with the total current amount available to the strategy to deploy. This is a permissioned function so if desired it could be used for illiquid or manipulatable strategies to compound rewards, perform maintenance or deposit/withdraw funds. This will not cause any change in PPS. Total assets will be the same before and after. A report() call will be needed to record any profits or losses.
function tend() external nonReentrant onlyKeepers;
shutdownStrategy
Used to shutdown the strategy preventing any further deposits.
Can only be called by the current management or emergencyAdmin.
This will stop any new deposit or {mint} calls but will
not prevent {withdraw} or {redeem}. It will also still allow for
{tend} and {report} so that management can report any last losses
in an emergency as well as provide any maintenance to allow for full
withdraw.
This is a one way switch and can never be set back once shutdown.
function shutdownStrategy() external onlyEmergencyAuthorized;
emergencyWithdraw
To manually withdraw funds from the yield source after a strategy has been shutdown.
This can only be called post shutdownStrategy. This will never cause a change in PPS. Total assets will be the same before and after. A strategist will need to override the {_emergencyWithdraw} function in their strategy for this to work.
function emergencyWithdraw(uint256 amount) external nonReentrant onlyEmergencyAuthorized;
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | Amount of asset to withdraw |
asset
Get the underlying asset for the strategy.
function asset() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | asset_ Underlying asset token address |
apiVersion
Get the API version for this TokenizedStrategy.
function apiVersion() external pure returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | version API version string |
management
Get the current address that controls the strategy.
function management() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | management_ Address of management. |
pendingManagement
Get the current pending management address if any.
function pendingManagement() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | pendingManagement_ Address of pending management. |
keeper
Get the current address that can call tend and report.
function keeper() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | keeper_ Address of the keeper. |
emergencyAdmin
Get the current address that can shutdown and emergency withdraw.
function emergencyAdmin() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | emergencyAdmin_ Address of the emergency admin. |
dragonRouter
Get the current dragon router address that will receive minted shares.
function dragonRouter() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | dragonRouter_ Address of the dragon router. |
pendingDragonRouter
Get the pending dragon router address if any.
function pendingDragonRouter() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | pendingDragonRouter_ Address of the pending dragon router. |
dragonRouterChangeTimestamp
Get the timestamp when dragon router change was initiated.
function dragonRouterChangeTimestamp() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | changeTimestamp Timestamp when change initiated in seconds (0 if no pending change) |
lastReport
The timestamp of the last time yield was reported.
function lastReport() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | lastReport_ Last report timestamp in seconds |
pricePerShare
Get the price per share.
Limited precision; use convertToAssets/convertToShares for exactness.
function pricePerShare() public view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | pps Price per share |
isShutdown
Check if the strategy has been shutdown.
function isShutdown() external view returns (bool);
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | isShutdown_ True if the strategy is shutdown. |
enableBurning
Get whether burning shares from dragon router during loss protection is enabled.
function enableBurning() external view returns (bool);
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | Whether the burning mechanism is enabled. |
setPendingManagement
Step one of two to set a new address to be in charge of the strategy.
Can only be called by the current management. The address is
set to pending management and will then have to call acceptManagement
in order for the 'management' to officially change.
Cannot set management to address(0).
function setPendingManagement(address _management) external onlyManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_management | address | New address to set pendingManagement to. |
acceptManagement
Step two of two to set a new 'management' of the strategy.
Can only be called by the current pendingManagement.
function acceptManagement() external;
setKeeper
Sets a new address to be in charge of tend and reports.
Can only be called by the current management.
function setKeeper(address _keeper) external onlyManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_keeper | address | New address to set keeper to. |
setEmergencyAdmin
Sets a new address to be able to shutdown the strategy.
Can only be called by the current management.
function setEmergencyAdmin(address _emergencyAdmin) external onlyManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_emergencyAdmin | address | New address to set emergencyAdmin to. |
setDragonRouter
Initiates a change to a new dragon router address with a cooldown period.
*Starts a two-step process to change the donation destination:
- Emits PendingDragonRouterChange(new, effectiveTimestamp)
- Enforces a cooldown of DRAGON_ROUTER_COOLDOWN (14 days) before finalization During the cooldown, users are notified and can exit if they disagree with the change.*
Reverts if _dragonRouter equals current dragonRouter (no-op protection)
function setDragonRouter(address _dragonRouter) external onlyManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_dragonRouter | address | New address to set as pending dragonRouter. |
finalizeDragonRouterChange
Finalizes the dragon router change after the cooldown period.
Requires a pending router and that the cooldown has elapsed. Emits UpdateDragonRouter(newDragonRouter) and clears the pending state.
Note: security: Permissionless - anyone can finalize after cooldown (by design)
function finalizeDragonRouterChange() external virtual;
cancelDragonRouterChange
Cancels a pending dragon router change.
Resets pending router and timestamp. Emits PendingDragonRouterChange(address(0), 0).
function cancelDragonRouterChange() external onlyManagement;
setName
Updates the name for the strategy.
function setName(string calldata _name) external onlyManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_name | string | New strategy name |
setEnableBurning
Sets whether to enable burning shares from dragon router during loss protection.
Can only be called by the current management.
function setEnableBurning(bool _enableBurning) external onlyManagement;
Parameters
| Name | Type | Description |
|---|---|---|
_enableBurning | bool | Whether to enable the burning mechanism. |
name
Returns the name of the token.
function name() external view returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | name_ Token name |
symbol
Returns the symbol of the strategy token.
Will be 'os' + asset symbol.
function symbol() external view returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | symbol_ Token symbol |
decimals
Returns the number of decimals used for user representation.
function decimals() external view returns (uint8);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint8 | decimals_ Decimals used by strategy and asset |
balanceOf
Returns the current balance for a given account.
function balanceOf(address account) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | Address to check balance for |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | balance_ Current balance in shares |
_balanceOf
Internal implementation of balanceOf.
function _balanceOf(StrategyData storage S, address account) internal view returns (uint256);
transfer
Transfer amount of shares from msg.sender to to.
Requirements:
tocannot be the zero address.tocannot be the address of the strategy.- the caller must have a balance of at least
_amount.*
function transfer(address to, uint256 amount) external virtual returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
to | address | Address receiving the shares |
amount | uint256 | Amount of shares to transfer |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | success True if the operation succeeded. |
allowance
Returns the remaining number of tokens that spender will be
allowed to spend on behalf of owner through transferFrom. This is
zero by default.
This value changes when {approve} or {transferFrom} are called.
function allowance(address owner, address spender) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | Address that owns the shares |
spender | address | Address authorized to move shares |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | remaining Remaining shares spender can move |
_allowance
Internal implementation of allowance.
function _allowance(StrategyData storage S, address owner, address spender) internal view returns (uint256);
approve
Sets amount as the allowance of spender over the caller's tokens.
NOTE: If amount is the maximum uint256, the allowance is not updated on
transferFrom. This is semantically equivalent to an infinite approval.
Requirements:
spendercannot be the zero address. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an Approval event.*
function approve(address spender, uint256 amount) external returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
spender | address | the address to allow the shares to be moved by. |
amount | uint256 | the amount of shares to allow spender to move. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | success True if the operation succeeded. |
transferFrom
Transfer amount of shares from from to to using the
allowance mechanism. amount is then deducted from the caller's
allowance.
Emits an Approval event indicating the updated allowance. This is not
required by the EIP.
NOTE: Does not update the allowance if the current allowance
is the maximum uint256.
Requirements:
fromandtocannot be the zero address.tocannot be the address of the strategy.frommust have a balance of at leastamount.- the caller must have allowance for
from's tokens of at leastamount. Emits a {Transfer} event.*
function transferFrom(address from, address to, uint256 amount) external virtual returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
from | address | the address to be moving shares from. |
to | address | the address to be moving shares to. |
amount | uint256 | the quantity of shares to move. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | success True if the operation succeeded. |
_transfer
*Moves amount of tokens from from to to.
This internal function is equivalent to transfer, and can be used to
e.g. implement automatic token fees, slashing mechanisms, etc.
Emits a {Transfer} event.
Requirements:
fromcannot be the zero address.tocannot be the zero address.tocannot be the strategies addressfrommust have a balance of at leastamount.*
function _transfer(StrategyData storage S, address from, address to, uint256 amount) internal;
_mint
*Creates amount tokens and assigns them to account, increasing
the total supply.
Emits a Transfer event with from set to the zero address.
Requirements:
accountcannot be the zero address.*
function _mint(StrategyData storage S, address account, uint256 amount) internal;
_burn
*Destroys amount tokens from account, reducing the
total supply.
Emits a Transfer event with to set to the zero address.
Requirements:
accountcannot be the zero address.accountmust have at leastamounttokens.*
function _burn(StrategyData storage S, address account, uint256 amount) internal;
_approve
*Sets amount as the allowance of spender over the owner s tokens.
This internal function is equivalent to approve, and can be used to
e.g. set automatic allowances for certain subsystems, etc.
Emits an Approval event.
Requirements:
ownercannot be the zero address.spendercannot be the zero address.*
function _approve(StrategyData storage S, address owner, address spender, uint256 amount) internal;
_spendAllowance
Updates owner s allowance for spender based on spent amount.
Does not update the allowance amount in case of infinite allowance.
Revert if not enough allowance is available.
Might emit an Approval event.
function _spendAllowance(StrategyData storage S, address owner, address spender, uint256 amount) internal;
nonces
Returns the current nonce for owner. This value must be
included whenever a signature is generated for permit.
Every successful call to permit increases owner's nonce by one. This
prevents a signature from being used multiple times.
function nonces(address _owner) external view returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
_owner | address | Address to return nonce for |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | nonce_ Current nonce for permit operations |
permit
Sets value as the allowance of spender over owner's tokens,
given owner's signed approval.
*IMPORTANT: The same issues IERC20-approve has related to transaction ordering also apply here. Emits an {Approval} event. Requirements:
spendercannot be the zero address.deadlinemust be a timestamp in the future.v,randsmust be a validsecp256k1signature fromownerover the EIP712-formatted function arguments.- the signature must use
owner's current nonce (see {nonces}). For more information on the signature format, see the https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section].*
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
DOMAIN_SEPARATOR
Returns the EIP-712 domain separator used by permit.
function DOMAIN_SEPARATOR() public view returns (bytes32);
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | domainSeparator Domain separator for permit calls |
Events
StrategyShutdown
Emitted when a strategy is shutdown.
event StrategyShutdown();
NewTokenizedStrategy
Emitted on the initialization of any new strategy that uses asset
with this specific apiVersion.
event NewTokenizedStrategy(address indexed strategy, address indexed asset, string apiVersion);
Reported
Emitted when the strategy reports profit or loss.
event Reported(uint256 profit, uint256 loss);
Parameters
| Name | Type | Description |
|---|---|---|
profit | uint256 | Profit amount |
loss | uint256 | Loss amount |
UpdateKeeper
Emitted when the 'keeper' address is updated to 'newKeeper'.
event UpdateKeeper(address indexed newKeeper);
Parameters
| Name | Type | Description |
|---|---|---|
newKeeper | address | Address authorized to call report/tend |
UpdateManagement
Emitted when the 'management' address is updated to 'newManagement'.
event UpdateManagement(address indexed newManagement);
Parameters
| Name | Type | Description |
|---|---|---|
newManagement | address | Address with management permissions |
UpdateEmergencyAdmin
Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'.
event UpdateEmergencyAdmin(address indexed newEmergencyAdmin);
Parameters
| Name | Type | Description |
|---|---|---|
newEmergencyAdmin | address | Address authorized for emergency operations |
UpdatePendingManagement
Emitted when the pendingManagement address is updated.
event UpdatePendingManagement(address indexed newPendingManagement);
Parameters
| Name | Type | Description |
|---|---|---|
newPendingManagement | address | Address pending to become management |
Approval
Emitted when the allowance of a spender for an owner is set by
a call to approve. value is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
Transfer
Emitted when value tokens are moved from one account (from) to
another (to).
Note that value may be zero.
event Transfer(address indexed from, address indexed to, uint256 value);
Deposit
Emitted when the caller has exchanged assets for shares,
and transferred those shares to owner.
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
Withdraw
Emitted when the caller has exchanged owners shares for assets,
and transferred those assets to receiver.
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
UpdateDragonRouter
Emitted when the dragon router address is updated.
event UpdateDragonRouter(address indexed newDragonRouter);
Parameters
| Name | Type | Description |
|---|---|---|
newDragonRouter | address | Address receiving minted profit shares |
PendingDragonRouterChange
Emitted when a pending dragon router change is initiated.
event PendingDragonRouterChange(address indexed newDragonRouter, uint256 effectiveTimestamp);
Parameters
| Name | Type | Description |
|---|---|---|
newDragonRouter | address | Address proposed to receive profit shares |
effectiveTimestamp | uint256 | Timestamp when change can be finalized in seconds |
UpdateBurningMechanism
Emitted when the burning mechanism is enabled or disabled.
event UpdateBurningMechanism(bool enableBurning);
Structs
StrategyData
Core storage struct for all strategy state
*All strategy state is stored in this single struct to enable the proxy pattern and avoid storage collisions between implementation and proxy contracts CRITICAL: This struct uses a custom storage slot (BASE_STRATEGY_STORAGE) following ERC-7201 namespaced storage pattern to ensure no collisions when using delegatecall from strategies. When extending strategies, NEVER add state variables outside this struct - extend the struct or use custom slots. STORAGE EFFICIENCY:
- Loading the struct slot does NOT load struct contents into memory
- Accessing individual fields only loads those specific storage slots
- Packing is disabled for clarity (see solhint-disable comment) GAS OPTIMIZATION:
- Multiple variables combined to reduce storage slot loads
- uint96 used for timestamps (sufficient until year 2^96/31556952 ≈ 2.5 billion years)
- uint8 used for flags where range is sufficient*
struct StrategyData {
mapping(address => uint256) nonces;
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowances;
ERC20 asset;
string name;
uint256 totalSupply;
uint256 totalAssets;
address keeper;
uint96 lastReport;
address management;
address pendingManagement;
address emergencyAdmin;
address dragonRouter;
address pendingDragonRouter;
uint96 dragonRouterChangeTimestamp;
uint8 decimals;
uint8 entered;
bool shutdown;
bool enableBurning;
}