What is a strategy?
A strategy is a set of steps to execute an investment in one or several protocols: holding an asset, farming and auto-compounding rewards, leveraged lending, leveraged farming. A vault does not know any of those steps. It hands assets to a strategy and asks what they are worth now. Everything protocol-specific lives behind the strategy trait, which is why a new strategy can be added without touching the vault. For a worked example, see Blend Autocompound Strategy.The Strategy Trait
A strategy implementsDeFindexStrategyTrait. Six methods, and any vault can allocate to your contract.
The six methods
from is always the vault.
__constructor: the underlying asset plusinit_args, a free-formVec<Val>whose shape you define. Pool addresses, routers, thresholds, the keeper. Validate here.depositandwithdraw: return the vault’s balance after the operation, not the amount moved.from.require_auth()first.balance: the vault’s position at the live protocol rate. Do not cache the rate.
The one rule that matters.deposit,withdrawandbalancereturn underlying asset, never internal shares or protocol receipt tokens. The vault prices its dfTokens off that number, so a strategy that returns bTokens gives every user of the vault a wrong share price.
harvest is every active action the position needs: claiming and compounding rewards, adjusting a leverage ratio, rebalancing a drifting position, rolling an expiring one. Deposit and withdraw are driven by the vault; harvest is driven by nobody, which is why it needs a keeper. data is an optional opaque blob, by convention a big-endian i128 minimum amount out so a harvest that swaps cannot be sandwiched. With nothing to do, return Ok(()) rather than failing.
Authorization
Address-based, throughrequire_auth(). The trait defines no roles, but you need one: the keeper, the address allowed to call harvest. Store it yourself, check it twice, and allow rotation signed by the current keeper.
env.authorize_as_current_contract() with the matching sub-invocations. Exercise that with real auth in at least one test, not only mock_all_auths().
Events
Emit the ones fromdefindex_strategy_core::event. Indexers, the API and the APY charts read them, so a silent strategy is invisible in the product even when it works.
price_per_share is what DeFindex computes your APY from. See Strategies APY.
Errors
ReturnStrategyError rather than panicking: NotInitialized (401), InvalidArgument (411), InsufficientBalance (412), InvalidSharesMinted (416), OnlyPositiveAmountAllowed (417), NotAuthorized (418).
Share accounting
Several vaults can use one strategy at once, so track shares per vault and convert to underlying on the way out. ERC-4626, with the upstream rate refreshed on every touch.- Rounding direction. In the protocol’s favour on the way in, the user’s disfavour on the way out. The other way round leaks value one stroop at a time.
- The inflation attack. The first depositor can donate assets so later deposits round to zero shares. Burn a small fixed amount of shares on the first deposit, permanently.
Before you ship
deposit,withdrawandbalancereturn underlying, at the live rate.from.require_auth()on deposit and withdraw, keeper check on harvest.- All three events emitted, with a real
price_per_share. - First-deposit inflation guard in place.
- Rounding tested in both directions.
wasm32v1-none, optimize with stellar contract optimize, and test against the upstream protocol deployed locally rather than a mock of it. The Blend Autocompound Strategy does all of the above, and its tests cover the inflation and rounding attacks explicitly.
Strategies APY
Every strategy earns differently, so DeFindex does not try to model lending rates, emission schedules and reward prices per protocol. It reads one number off theHarvestEvent: the price per share.
365.2425 is the average length of a year, leap years included.
Example. A price per share of 1.10 today against 1.00 thirty days ago is an ROI of 0.10, which annualizes to .
This is why the trait insists on emitting HarvestEvent with a real price_per_share. A strategy that does not is invisible to the APY charts, the API and the indexer.