Skip to main content

YieldDonatingTokenizedStrategy

Git Source

Inherits: TokenizedStrategy

Title: YieldDonatingTokenizedStrategy

Author: Golem Foundation

Specialized TokenizedStrategy for productive assets with discrete harvesting; profits are donated by minting shares to the dragon router.

Behavior overview:

  • On report(), harvests the underlying position via BaseStrategy.harvestAndReport()
  • If newTotalAssets > oldTotalAssets, mints shares equal to the profit (asset value) to the dragon router
  • If losses occur and burning is enabled, burns dragon router shares (up to its balance) using rounding-down shares-to-burn
  • No tracked-loss bucket exists; any loss not covered by dragon router burning reduces totalAssets and affects PPS for all holders Economic notes:
  • Profit donations are realized via share mints at the time of report
  • Losses first attempt dragon share burning when enabled; residual losses decrease PPS
  • Dragon router change follows TokenizedStrategy cooldown and two-step finalization Terminal-state recovery (operator-managed):
  • After a catastrophic loss that reduces totalAssets to 0 while totalSupply remains positive (all dragon shares burned and residual loss socialized), the strategy enters a terminal state: _convertToShares returns 0 while tracked assets are 0, so deposit() and mint() revert until accounting is restored.
  • A direct donation followed by report() can heal that state. When report() observes recovered assets from a zero-asset state, surplus recovery shares are minted to the dragon router as donation yield. This restores a usable PPS without assigning the recovered surplus to stale dust holders.

Note: security-contact: [email protected]

Quick Start - What Matters Most

This is the implementation for realized-profit donation flows. A keeper calls report(); the strategy harvests, reports current assets, and mints any profit shares to the donation address.

Key functions to understand:

  • report() - Realizes profit/loss and mints donation shares when profit is non-zero
  • _harvestAndReport() - Strategy-specific hook that returns total assets
  • deposit / mint - Seed permanently locked first shares on an empty yield-donating strategy
  • setEnableBurning - Toggles loss protection; disabling from an enabled state can revert until pending dragon shares are reported
  • reportAndDisableBurning - Reports current accounting and disables burn protection atomically

1.2.0-develop.15 Behavior Notes

Minimum liquidity on the first deposit

Yield-donating strategies permanently lock the first 1_000 shares at address(0xdead), following the Uniswap V2 minimum-liquidity pattern. On the first successful deposit into an empty strategy, the depositor receives assets - 1000 shares and 1000 shares are minted to 0x000000000000000000000000000000000000dEaD.

Consequences for integrations and tests:

  • A first deposit with assets <= 1000 reverts through the zero-share path.
  • previewMint(shares) on an empty strategy returns shares + 1000.
  • maxDeposit(receiver) returns zero on an empty strategy when upstream deposit capacity is <= 1000.
  • maxMint(receiver) and maxRedeem(owner) return zero in the zero-asset terminal state.
  • Tutorial tests must not assert that the first YDS deposit mints shares 1:1.

Relevant constants:

uint256 internal constant MINIMUM_LIQUIDITY = 1_000;
address internal constant MINIMUM_LIQUIDITY_RECEIVER = address(0xdead);

Disabling dragon-share burning

When burning is enabled, setEnableBurning(false) requires the current dragon-router share balance to be zero. If pending dragon shares exist, the call reverts with report before disabling burning. Management should use reportAndDisableBurning() when it wants to report current accounting and disable burn protection in one transaction.

function setEnableBurning(bool _enableBurning) external override onlyManagement;
function reportAndDisableBurning() external onlyManagement returns (uint256 profit, uint256 loss);

Constants

MINIMUM_LIQUIDITY

Permanently locked first shares, following the Uniswap V2 minimum-liquidity pattern

uint256 internal constant MINIMUM_LIQUIDITY = 1_000

MINIMUM_LIQUIDITY_RECEIVER

Receiver for permanently locked minimum-liquidity shares

address internal constant MINIMUM_LIQUIDITY_RECEIVER = address(0xdead)

Functions

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) public virtual override 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

_convertToShares

Yield-donating first deposits fund permanently locked shares.

function _convertToShares(StrategyData storage S, uint256 assets, Math.Rounding _rounding)
internal
view
virtual
override
returns (uint256);

previewMint

Previews assets required to mint exact shares

Uses Ceil rounding. Marked virtual so strategies that gate the real mint path (e.g. yield-skimming on insolvency) can mirror the gate in the preview and stay ERC-4626-compliant.

function previewMint(uint256 shares) public view virtual override returns (uint256 assets);

Parameters

NameTypeDescription
sharesuint256Amount of shares to mint

Returns

NameTypeDescription
assetsuint256assets_ Required assets for mint

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) public virtual override 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

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 override returns (uint256);

Parameters

NameTypeDescription
receiveraddressAddress that would receive the shares

Returns

NameTypeDescription
<none>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 override returns (uint256);

Parameters

NameTypeDescription
receiveraddressAddress that would receive the shares

Returns

NameTypeDescription
<none>uint256_maxMint Maximum shares that can be minted

maxRedeem

Maximum number of shares that can be redeemed by owner.

function maxRedeem(address owner) public view virtual override returns (uint256);

Parameters

NameTypeDescription
owneraddressAddress that owns the shares

Returns

NameTypeDescription
<none>uint256_maxRedeem Maximum shares that can be redeemed

_deposit

Seeds permanently locked shares on the first successful yield-donating deposit/mint.

function _deposit(StrategyData storage S, address receiver, uint256 assets, uint256 shares)
internal
virtual
override;

_addMinimumLiquidity

function _addMinimumLiquidity(uint256 shares) internal pure returns (uint256);

_maxDepositWithMinimumLiquidity

function _maxDepositWithMinimumLiquidity(StrategyData storage S, address receiver)
internal
view
returns (uint256 maxAssets);

_isZeroAssetTerminalState

function _isZeroAssetTerminalState(StrategyData storage S) internal view returns (bool);

report

Reports strategy performance and distributes profits as donations

Mints profit-derived shares to dragon router when newTotalAssets > oldTotalAssets; on loss, attempts dragon share burning if enabled. Residual loss reduces PPS (no tracked-loss bucket). Keeper trust assumption: report() timing is at the keeper's discretion and directly controls when dragon shares mint (on profit) and burn (on loss). A compromised keeper can time calls adversarially — for example, call report() during a temporary dip to burn dragon shares at the depressed PPS and then call again on recovery so the rebound is captured as fresh dragon-mint profit rather than offsetting the earlier dip; or delay report() through a real loss to let users exit at a stale, inflated PPS and socialise the loss across remaining holders. These paths are bounded by the dragon router's share balance and degrade yield quality rather than drain funds, but they are genuine keeper-side risks. The keeper is a SEMI-TRUSTED role. Mitigation is operational: keeper key custody under multisig / MPC, off-chain alerting on report() calls during volatility spikes, and the no-cooldown setKeeper() rotation path if the key is compromised. shutdownStrategy can be used as containment to halt new deposits/mints while rotation and assessment happen, but it does not block tend() or report(). No on-chain cap on reporting cadence is enforced — that would constrain legitimate operation for a threat the trust model already accepts.

function report()
public
virtual
override(TokenizedStrategy)
nonReentrant
onlyKeepers
returns (uint256 profit, uint256 loss);

setEnableBurning

Sets whether dragon-share burning is enabled for loss protection.

Yield-donating strategies learn their authoritative current asset value through report(). When dragon shares exist, disabling burning without reporting first could retroactively preserve dragon shares through an unreported loss. Use reportAndDisableBurning() in that case.

function setEnableBurning(bool _enableBurning) external override onlyManagement;

Parameters

NameTypeDescription
_enableBurningboolWhether to enable the burning mechanism

reportAndDisableBurning

Reports current accounting, then disables dragon burn loss protection.

This explicit helper gives operators an atomic path for disabling burning without leaving a between-transaction window for unreported losses.

function reportAndDisableBurning() external onlyManagement returns (uint256 profit, uint256 loss);

Returns

NameTypeDescription
profituint256Profit reported by the accounting sync
lossuint256Loss reported by the accounting sync

_handleDragonLossProtection

Internal function to handle loss protection for dragon principal

function _handleDragonLossProtection(StrategyData storage S, uint256 loss) internal;

Parameters

NameTypeDescription
SStrategyDataStorage struct pointer to access strategy's storage variables
lossuint256Amount of loss to protect against in asset base units If burning is enabled, this function will try to burn shares from the dragon router equivalent to the loss amount.

Events

DonationMinted

Emitted when profit or recovery shares are minted

event DonationMinted(address indexed dragonRouter, uint256 amount);

Parameters

NameTypeDescription
dragonRouteraddressAddress receiving minted donation or recovery shares
amountuint256Amount of shares minted in share base units

DonationBurned

Emitted when dragon shares are burned to cover losses

event DonationBurned(address indexed dragonRouter, uint256 amount);

Parameters

NameTypeDescription
dragonRouteraddressAddress whose shares are burned
amountuint256Amount of shares burned in share base units