Skip to main content

TokenizedStrategy

Git Source

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:

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

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

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

NameTypeDescription
_senderaddressAddress to validate (typically msg.sender)

_strategyStorage

Returns a storage pointer to the StrategyData struct

function _strategyStorage() internal pure returns (StrategyData storage S);

Returns

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

  1. Validates not already initialized
  2. Sets asset and derives decimals
  3. Sets strategy name
  4. Initializes lastReport to current timestamp
  5. Sets all role addresses (with zero-address validation)
  6. Configures burning mechanism
  7. 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

NameTypeDescription
_assetaddressAddress of the underlying ERC20 asset (cannot be zero)
_namestringHuman-readable name for strategy shares (e.g., "Octant Lido ETH Strategy")
_managementaddressAddress for primary admin (cannot be zero)
_keeperaddressAddress authorized to call report/tend (cannot be zero)
_emergencyAdminaddressAddress authorized for emergency actions (cannot be zero)
_dragonRouteraddressAddress to receive minted profit shares (cannot be zero, OCTANT-specific)
_enableBurningboolWhether 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

NameTypeDescription
assetsuint256Amount of assets to deposit (or type(uint256).max for full balance)
receiveraddressAddress to receive the minted shares

Returns

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

NameTypeDescription
sharesuint256Exact amount of shares to mint
receiveraddressAddress to receive the minted shares

Returns

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

NameTypeDescription
assetsuint256Amount of assets to withdraw
receiveraddressAddress to receive the withdrawn assets
owneraddressAddress whose shares will be burned

Returns

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

NameTypeDescription
assetsuint256Amount of assets to withdraw
receiveraddressAddress to receive the withdrawn assets
owneraddressAddress whose shares are burned
maxLossuint256Maximum acceptable loss in basis points (0-10000, where 10000 = 100%)

Returns

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

NameTypeDescription
sharesuint256Amount of shares to burn
receiveraddressAddress to receive the withdrawn assets
owneraddressAddress whose shares are burned

Returns

NameTypeDescription
&lt;none&gt;uint256assets 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

NameTypeDescription
sharesuint256Amount of shares to burn
receiveraddressAddress to receive the withdrawn assets
owneraddressAddress whose shares are burned
maxLossuint256Maximum acceptable loss in basis points (0-10000, where 10000 = 100%)

Returns

NameTypeDescription
&lt;none&gt;uint256assets 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

NameTypeDescription
&lt;none&gt;uint256totalAssets_ 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

NameTypeDescription
&lt;none&gt;uint256totalSupply_ 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

NameTypeDescription
assetsuint256Amount of assets to convert

Returns

NameTypeDescription
&lt;none&gt;uint256shares_ 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

NameTypeDescription
sharesuint256Amount of shares to convert

Returns

NameTypeDescription
&lt;none&gt;uint256assets_ 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

NameTypeDescription
assetsuint256Amount of assets to deposit

Returns

NameTypeDescription
&lt;none&gt;uint256shares_ 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

NameTypeDescription
sharesuint256Amount of shares to mint

Returns

NameTypeDescription
&lt;none&gt;uint256assets_ 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

NameTypeDescription
assetsuint256Amount of assets to withdraw

Returns

NameTypeDescription
&lt;none&gt;uint256shares_ 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

NameTypeDescription
sharesuint256Amount of shares to redeem

Returns

NameTypeDescription
&lt;none&gt;uint256assets_ 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

NameTypeDescription
receiveraddressAddress that would receive the shares

Returns

NameTypeDescription
&lt;none&gt;uint256max 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

NameTypeDescription
receiveraddressAddress that would receive the shares

Returns

NameTypeDescription
&lt;none&gt;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

NameTypeDescription
owneraddressAddress that owns the shares

Returns

NameTypeDescription
&lt;none&gt;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

NameTypeDescription
owneraddressAddress that owns the shares

Returns

NameTypeDescription
&lt;none&gt;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

NameTypeDescription
profituint256Notional amount of gain since last report report in terms of asset.
lossuint256Notional 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

NameTypeDescription
amountuint256Amount of asset to withdraw

asset

Get the underlying asset for the strategy.

function asset() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addressasset_ Underlying asset token address

apiVersion

Get the API version for this TokenizedStrategy.

function apiVersion() external pure returns (string memory);

Returns

NameTypeDescription
&lt;none&gt;stringversion API version string

management

Get the current address that controls the strategy.

function management() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addressmanagement_ Address of management.

pendingManagement

Get the current pending management address if any.

function pendingManagement() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addresspendingManagement_ Address of pending management.

keeper

Get the current address that can call tend and report.

function keeper() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addresskeeper_ Address of the keeper.

emergencyAdmin

Get the current address that can shutdown and emergency withdraw.

function emergencyAdmin() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addressemergencyAdmin_ Address of the emergency admin.

dragonRouter

Get the current dragon router address that will receive minted shares.

function dragonRouter() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addressdragonRouter_ Address of the dragon router.

pendingDragonRouter

Get the pending dragon router address if any.

function pendingDragonRouter() external view returns (address);

Returns

NameTypeDescription
&lt;none&gt;addresspendingDragonRouter_ Address of the pending dragon router.

dragonRouterChangeTimestamp

Get the timestamp when dragon router change was initiated.

function dragonRouterChangeTimestamp() external view returns (uint256);

Returns

NameTypeDescription
&lt;none&gt;uint256changeTimestamp 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

NameTypeDescription
&lt;none&gt;uint256lastReport_ 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

NameTypeDescription
&lt;none&gt;uint256pps Price per share

isShutdown

Check if the strategy has been shutdown.

function isShutdown() external view returns (bool);

Returns

NameTypeDescription
&lt;none&gt;boolisShutdown_ 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

NameTypeDescription
&lt;none&gt;boolWhether 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

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

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

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

  1. Emits PendingDragonRouterChange(new, effectiveTimestamp)
  2. 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

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

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

NameTypeDescription
_enableBurningboolWhether to enable the burning mechanism.

name

Returns the name of the token.

function name() external view returns (string memory);

Returns

NameTypeDescription
&lt;none&gt;stringname_ Token name

symbol

Returns the symbol of the strategy token.

Will be 'os' + asset symbol.

function symbol() external view returns (string memory);

Returns

NameTypeDescription
&lt;none&gt;stringsymbol_ Token symbol

decimals

Returns the number of decimals used for user representation.

function decimals() external view returns (uint8);

Returns

NameTypeDescription
&lt;none&gt;uint8decimals_ Decimals used by strategy and asset

balanceOf

Returns the current balance for a given account.

function balanceOf(address account) external view returns (uint256);

Parameters

NameTypeDescription
accountaddressAddress to check balance for

Returns

NameTypeDescription
&lt;none&gt;uint256balance_ 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:

  • to cannot be the zero address.
  • to cannot 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

NameTypeDescription
toaddressAddress receiving the shares
amountuint256Amount of shares to transfer

Returns

NameTypeDescription
&lt;none&gt;boolsuccess 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

NameTypeDescription
owneraddressAddress that owns the shares
spenderaddressAddress authorized to move shares

Returns

NameTypeDescription
&lt;none&gt;uint256remaining 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:

  • spender cannot 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

NameTypeDescription
spenderaddressthe address to allow the shares to be moved by.
amountuint256the amount of shares to allow spender to move.

Returns

NameTypeDescription
&lt;none&gt;boolsuccess 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:

  • from and to cannot be the zero address.
  • to cannot be the address of the strategy.
  • from must have a balance of at least amount.
  • the caller must have allowance for from's tokens of at least amount. Emits a {Transfer} event.*
function transferFrom(address from, address to, uint256 amount) external virtual returns (bool);

Parameters

NameTypeDescription
fromaddressthe address to be moving shares from.
toaddressthe address to be moving shares to.
amountuint256the quantity of shares to move.

Returns

NameTypeDescription
&lt;none&gt;boolsuccess 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:

  • from cannot be the zero address.
  • to cannot be the zero address.
  • to cannot be the strategies address
  • from must have a balance of at least amount.*
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:

  • account cannot 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:

  • account cannot be the zero address.
  • account must have at least amount tokens.*
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:

  • owner cannot be the zero address.
  • spender cannot 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

NameTypeDescription
_owneraddressAddress to return nonce for

Returns

NameTypeDescription
&lt;none&gt;uint256nonce_ 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:

  • spender cannot be the zero address.
  • deadline must be a timestamp in the future.
  • v, r and s must be a valid secp256k1 signature from owner over 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

NameTypeDescription
&lt;none&gt;bytes32domainSeparator 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

NameTypeDescription
profituint256Profit amount
lossuint256Loss amount

UpdateKeeper

Emitted when the 'keeper' address is updated to 'newKeeper'.

event UpdateKeeper(address indexed newKeeper);

Parameters

NameTypeDescription
newKeeperaddressAddress authorized to call report/tend

UpdateManagement

Emitted when the 'management' address is updated to 'newManagement'.

event UpdateManagement(address indexed newManagement);

Parameters

NameTypeDescription
newManagementaddressAddress with management permissions

UpdateEmergencyAdmin

Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'.

event UpdateEmergencyAdmin(address indexed newEmergencyAdmin);

Parameters

NameTypeDescription
newEmergencyAdminaddressAddress authorized for emergency operations

UpdatePendingManagement

Emitted when the pendingManagement address is updated.

event UpdatePendingManagement(address indexed newPendingManagement);

Parameters

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

NameTypeDescription
newDragonRouteraddressAddress receiving minted profit shares

PendingDragonRouterChange

Emitted when a pending dragon router change is initiated.

event PendingDragonRouterChange(address indexed newDragonRouter, uint256 effectiveTimestamp);

Parameters

NameTypeDescription
newDragonRouteraddressAddress proposed to receive profit shares
effectiveTimestampuint256Timestamp 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;
}