Available Code
Code Structure & Playbooks
Deep dives, napkin implementations, and operational checklists for OctantFoundry builders. Use this page as the canonical reference when translating the docs into production-grade strategies and mechanisms.
Napkin Implementation — sDAI (DAI → sDAI)
Addresses. DAI (underlying), sDAI at 0x83F20F44975D03b1b09E64809B757c47f942BEeA. The contract exposes full ERC‑4626 semantics backed by DSR accounting (chi/rho/drip), so you never “claim rewards”; value accrues in the exchange rate surfaced by convertToAssets.
interface IERC4626 {
function asset() external view returns (address);
function balanceOf(address) external view returns (uint256);
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
function convertToAssets(uint256 shares) external view returns (uint256);
}
contract YDS_SDAI_Strategy is BaseHealthCheck {
IERC20 public immutable DAI;
IERC4626 public immutable sDAI;
/// Set up / initialize contract
...
function _deployFunds(uint256 amount) internal override {
sDAI.deposit(amount, address(this));
}
function _freeFunds(uint256 amount) internal override {
if (amount == 0) return;
sDAI.withdraw(amount, address(this), address(this));
}
function _harvestAndReport() internal override returns (uint256 _totalAssets) {
uint256 idle = DAI.balanceOf(address(this));
uint256 shares = IERC20(address(sDAI)).balanceOf(address(this));
_totalAssets = idle + sDAI.convertToAssets(shares);
}
/// Implement extra hooks
...
}Napkin Implementation — sUSDS (USDS → sUSDS)
Addresses. USDS (underlying), sUSDS at 0xa3931d71877C0e7A3148CB7Eb4463524FEc27fbD on Ethereum mainnet. The Sky docs specify ERC‑4626 deposit/withdraw semantics and “no fees.” The yield source is SSR, reflected in convertToAssets.
contract YDS_SUSDS_Strategy is BaseHealthCheck {
IERC20 public immutable USDS;
IERC4626 public immutable sUSDS;
...
function _deployFunds(uint256 amount) internal override {
sUSDS.deposit(amount, address(this));
}
function _freeFunds(uint256 amount) internal override {
sUSDS.withdraw(amount, address(this), address(this));
}
function _harvestAndReport() internal override returns (uint256 _totalAssets) {
uint256 idle = USDS.balanceOf(address(this));
uint256 shares = IERC20(address(sUSDS)).balanceOf(address(this));
_totalAssets = idle + sUSDS.convertToAssets(shares);
}
...
}Security — Mirror Upstream Limits & Add Safety Rails
Map upstream capacity into your strategy’s limits. Surface the wrapper’s maxDeposit/maxWithdraw as availableDepositLimit/availableWithdrawLimit so users get accurate gating.
function availableDepositLimit(address) public view override returns (uint256) {
return IERC4626(address(sDAI /* or sUSDS */)).maxDeposit(address(this));
}
function availableWithdrawLimit(address) public view override returns (uint256) {
return IERC4626(address(sDAI /* or sUSDS */)).maxWithdraw(address(this));
}Use BaseHealthCheck. Configure profitLimitRatio/lossLimitRatio and keep doHealthCheck enabled. If _harvestAndReport() returns an out‑of‑bounds delta, report() reverts before donation shares are minted/burned, preventing faulty accounting.
Foundry: Prove It on a Mainnet Fork
Setup. Pin a recent mainnet block; fund test EOAs with DAI/USDS; set keeper/management roles; approve the strategy for the underlying.
A. Deposit / Withdraw paths. Deposit → strategy shares minted → _deployFunds increases wrapper share balance. Withdraw → _freeFunds pulls underlying via wrapper and returns it to the user.
B. Profit interval (donation mint). Warp time so convertToAssets increases (DSR/SSR accrual). Keeper calls report(). Assert donation address share balance captures profit while depositor PPS remains steady.
TAM: Writing a New Funding Mechanism (1-person-1-vote)
Context. Implement a simple one-person-one-vote (1p1v) mechanism on top of the shared TokenizedAllocationMechanism core. Implement hooks to grant each voter one unit of power, enforce single votes, define quorum, and convert votes to shares while respecting budget.
Step 0 — Base & Storage Pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {BaseAllocationMechanism} from "./BaseAllocationMechanism.sol";
import {IERC20} from "./interfaces/IERC20.sol";
contract OnePersonOneVoteMechanism is BaseAllocationMechanism {
bytes32 private constant OPOV_SLOT = keccak256("tam.mechanism.1p1v.storage.v1");
struct OPOV {
mapping(address => bool) registered;
uint256 registeredCount;
mapping(uint256 => mapping(address => bool)) voted;
mapping(uint256 => uint256) forVotes;
mapping(uint256 => uint256) againstVotes;
uint256 totalForVotesSucceeded;
uint256 quorumNum;
uint256 quorumDen;
}
function _s() private pure returns (OPOV storage s) {
bytes32 slot = OPOV_SLOT;
assembly { s.slot := slot }
}
function setQuorum(uint256 num, uint256 den) external {
require(msg.sender == address(_tokenizedAllocation().owner()), "!owner");
require(den > 0 && num <= den, "bad quorum");
OPOV storage s = _s();
s.quorumNum = num; s.quorumDen = den;
}
}Step 1 — Registration
function _beforeSignupHook(address user) internal override returns (bool ok) {
OPOV storage s = _s();
if (!s.registered[user]) { s.registered[user] = true; s.registeredCount += 1; }
return true;
}
function _getVotingPowerHook(address user, uint256) internal view override returns (uint256) {
return _tokenizedAllocation().votingPower(user) == 0 ? 1 : 0;
}Step 2 — Proposal Policy
function _beforeProposeHook(address proposer) internal view override returns (bool) {
address mgmt = _tokenizedAllocation().management();
address keep = _tokenizedAllocation().keeper();
return (proposer == mgmt || proposer == keep);
}Step 3 — Voting Semantics
enum VoteType { Against, For, Abstain }
function _processVoteHook(
uint256 pid,
address voter,
VoteType choice,
uint256 weight,
uint256 oldPower
) internal override returns (uint256 newPower) {
OPOV storage s = _s();
require(!s.voted[pid][voter], "already voted");
require(weight == 1, "1p1v: weight must be 1");
s.voted[pid][voter] = true;
if (choice == VoteType.For) s.forVotes[pid] += 1;
else if (choice == VoteType.Against) s.againstVotes[pid] += 1;
require(oldPower >= 1, "insufficient power");
return oldPower - 1;
}Steps 4–7 — Quorum, Finalization, Conversion, Redemption
function _hasQuorumHook(uint256 pid) internal view override returns (bool) {
OPOV storage s = _s();
uint256 minVotes = (s.registeredCount * s.quorumNum) / s.quorumDen;
return s.forVotes[pid] >= minVotes && s.forVotes[pid] > s.againstVotes[pid];
}
function _beforeFinalizeVoteTallyHook() internal override returns (bool) {
OPOV storage s = _s();
s.totalForVotesSucceeded = 0;
uint256 n = _getProposalCount();
for (uint256 pid = 1; pid <= n; pid++) {
if (_hasQuorumHook(pid)) { s.totalForVotesSucceeded += s.forVotes[pid]; }
}
return true;
}
function _convertVotesToShares(uint256 pid) internal view override returns (uint256) {
OPOV storage s = _s();
require(s.totalForVotesSucceeded > 0, "no passing proposals");
uint256 assets = (_tokenizedAllocation().totalAssets() * s.forVotes[pid])
/ s.totalForVotesSucceeded;
return assets;
}
function _availableWithdrawLimit(address) internal view override returns (uint256) {
(uint48 start, uint48 grace) = (_tokenizedAllocation().globalRedemptionStart(),
_tokenizedAllocation().gracePeriod());
uint256 nowTs = block.timestamp;
if (nowTs < start || nowTs > start + grace) return 0;
return type(uint256).max;
}
function _requestCustomDistributionHook(address, uint256)
internal override returns (bool handled, uint256 assetsTransferred)
{
return (false, 0);
}
Foundry Checklist (PR-sized Tests)
- Registration remains idempotent;
votingPower(user)is 1 after first signup. - Proposal creation restricted to keeper/management (or relax via hook edits).
- Voting enforces single vote per proposal; tallies update once.
- Finalization snapshots
totalForVotesSucceeded. - Queueing mints shares ≤ budget; conversion uses floor rounding.
- Redemption window enforced; share transfers blocked outside window.
- Post-grace dust recoverable via sweep.
Operational Notes & Extensions
Open proposing. Return true in _beforeProposeHook to let anyone propose; rely solely on quorum.
Fixed-grant variant. Use a fixed grantShares per passing proposal, tracking remaining budget at finalization.
Access-controlled queuing. Enforce roles in _requestCustomDistributionHook if governance demands it, reporting assetsTransferred for accounting.
BaseStrategy: TokenizedStrategy Adapter
Author: Golem Foundation • Source: TokenizedStrategy reference implementation. Purpose: Provide a minimal abstract contract that inherits all TokenizedStrategy plumbing while delegating strategy-specific logic to three hooks: _deployFunds, _freeFunds, and _harvestAndReport.
Immutable wiring. The constructor pins the underlying asset, management/keeper/emergency roles, donation address, and the shared TokenizedStrategy implementation address (TOKENIZED_STRATEGY_ADDRESS). Calls that miss locally defined functions fall through the fallback and delegatecall into the implementation, meaning your strategy contract can safely define custom storage without collisions.
address public immutable TOKENIZED_STRATEGY_ADDRESS;
IERC20 internal immutable asset;
ITokenizedStrategy internal immutable TokenizedStrategy;
modifier onlySelf() {
_onlySelf();
_;
}
constructor(
address _asset,
string memory _name,
address _management,
address _keeper,
address _emergencyAdmin,
address _donationAddress,
bool _enableBurning,
address _tokenizedStrategyAddress
) {
TOKENIZED_STRATEGY_ADDRESS = _tokenizedStrategyAddress;
asset = IERC20(_asset);
TokenizedStrategy = ITokenizedStrategy(address(this));
// delegatecall init storage via _delegateCall(...)
}Lifecycle hooks. Implement the trio below in your concrete strategy:
- _deployFunds: Move freshly deposited assets into the yield source; be mindful of dust thresholds and sandwich protection.
- _freeFunds: Free liquidity for withdrawals; revert on illiquidity when losses would exceed
maxLoss. - _harvestAndReport: Harvest external rewards, convert to base asset, and return the canonical total balance; this drives profit/loss accounting.
Optional overrides. Strategies can define availableDepositLimit, availableWithdrawLimit, _tend, _tendTrigger, and _emergencyWithdraw for nuanced behavior. Externally visible wrappers such as deployFunds, freeFunds, and harvestAndReport are onlySelf gated to guarantee delegatecall context.
BaseYieldSkimmingHealthCheck
Author: Yearn → Golem Foundation adaptation. Wraps BaseStrategy with guardrails that ensure harvestAndReport never records out-of-band profits or losses during yield skimming.
bool public doHealthCheck = true;
uint16 private _profitLimitRatio = uint16(MAX_BPS); // default 100%
uint16 private _lossLimitRatio; // default 0
function harvestAndReport()
external
override
onlySelf
returns (uint256 _totalAssets)
{
_totalAssets = super.harvestAndReport();
if (doHealthCheck) {
_executeHealthCheck(_totalAssets);
}
}
function _executeHealthCheck(uint256 _totalAssets) internal virtual {
// Child implementations compare _totalAssets vs last report,
// revert when profit/loss breaches configured basis-point bands.
}Management knobs. Use setProfitLimitRatio / setLossLimitRatio (bps) to bound acceptable variance. Toggle the entire check via setDoHealthCheck; the next passing report re-enables it automatically.
Rate tracking. Yield-scaling strategies often rely on wrapper exchange rates. Override getCurrentRateRay and _executeHealthCheck to compare current vs previous rate, reverting if delta exceeds tolerance.
DebtManagementLib: Multistrategy Rebalancer
Purpose: Extract Yearn V3 vault rebalancing logic into a standalone Solidity library. Keeps multistrategy vault contracts below size limits while handling exact accounting for idle vs deployed capital, max loss tolerances, and shutdown flows.
function updateDebt(
mapping(address => StrategyParams) storage strategies,
uint256 totalIdle,
uint256 totalDebt,
address strategy,
uint256 targetDebt,
uint256 maxLoss,
uint256 minimumTotalIdle,
address asset,
bool isShutdown
) external returns (UpdateDebtResult memory result);Key behaviours.
- Increase debt: Move idle assets into strategies respecting
maxDepositandmaxDebt, never breachingminimumTotalIdle. - Reduce debt: Withdraw from strategies according to
maxRedeem; compute realized loss and revert if it exceedsmaxLossbps. - Shutdown override: When
isShutdownis true, forcestargetDebt = 0to unwind strategies.
Usage. After calling updateDebt, update vault storage with the returned newDebt, newTotalIdle, and newTotalDebt.
ERC20SafeApproveLib
Problem: Legacy tokens (USDT, BNB, etc.) implement non-standard approve that either return no bool or silently fail. This library wraps approvals in a low-level call that validates both the status flag and returned data when present.
function safeApprove(address token, address spender, uint256 amount) external {
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, amount));
if (!success || (data.length > 0 && !abi.decode(data, (bool)))) {
revert ApprovalFailed();
}
}Integration tips. Always reset allowance to zero before setting a new value when interacting with non-standard tokens. Use this helper from strategy constructors or pipeline steps that refresh allowances for routers, wrappers, or bridges.
MultistrategyLockedVault
Goal: Extend the multistrategy vault with a custody-based rage quit flow that prevents cooldown bypass attacks and adds a two-step governance delay for cooldown changes.
Custody Lifecycle
- Initiate:
initiateRageQuit(shares)locks exact shares; they become non-transferable until cooldown ends. - Cooldown: Track via
custodyInfo[user];maxWithdraw/maxRedeemreturn zero unless custody is active and unlocked. - Withdraw:
_processCustodyWithdrawalenforces share caps per custody and clears state when fully withdrawn.
Transfer guard. Overrides ERC20 _transfer to block moving locked shares. Query helpers: getTransferableShares and getRageQuitableShares.
Two-Step Cooldown Governance
- Propose: Regen governance calls
proposeRageQuitCooldownPeriodChange(must be 1–30 days). Starts 14-day grace period. - Grace: Users can rage quit under old duration; pending value stored in
pendingRageQuitCooldownPeriod. - Finalize: Anyone may call
finalizeRageQuitCooldownPeriodChangeafter the grace period. Governance may cancel mid-grace.
Example. With a 7-day current cooldown, governance proposes 14 days. Users initiating rage quit within the grace window complete in 7 days; after finalization, new rage quits adopt 14-day locks.
RegenEarningPowerCalculator
Author: Golem Foundation • Purpose: Gate staking rewards with configurable allow/block sets while keeping the earning-power calculation linear.
- Access modes:
NONE(permissionless),ALLOWSET(must appear in external allowset),BLOCKSET(everyone except listed addresses). - Earning power cap: Linear in staked amount but capped to
type(uint96).maxto fit downstream accounting. - Bump logic: Front-ends call
getNewEarningPowerto detect whether deposits should be rebalanced when access or stake changes.
function _hasAccess(address staker) internal view returns (bool) {
if (accessMode == AccessMode.NONE) return true;
if (accessMode == AccessMode.ALLOWSET) return allowset.contains(staker);
if (accessMode == AccessMode.BLOCKSET) return !blockset.contains(staker);
return false;
}
function getEarningPower(uint256 stakedAmount, address staker, address)
external
view
override
returns (uint256 earningPower)
{
if (!_hasAccess(staker)) return 0;
return stakedAmount > type(uint96).max ? type(uint96).max : stakedAmount;
}
function getNewEarningPower(
uint256 stakedAmount,
address staker,
address,
uint256 oldEarningPower
) external view override returns (uint256 newPower, bool qualifies) {
newPower = _hasAccess(staker)
? (stakedAmount > type(uint96).max ? type(uint96).max : stakedAmount)
: 0;
qualifies = newPower != oldEarningPower;
}Governance knobs. Owners can hot-swap allow/block sets and switch the access mode via setAccessMode; existing stakes require a bump to take effect.
RegenStaker with Delegation Surrogates
Pattern: Users stake governance tokens while delegating votes through lightweight surrogate contracts deployed deterministically per delegatee.
- Surrogate lifecycle: First delegation to an address deploys a
DelegationSurrogatevia CREATE2 (~300k gas); subsequent delegations reuse it (~5k gas). - Custody: Tokens live in the surrogate, which immediately delegates to the target address; the main staker contract retains withdrawal authority.
- Predictability: Front-ends can precompute surrogate addresses to surface gas estimates or pre-fund allowance flows.
function _fetchOrDeploySurrogate(address delegatee)
internal
override
returns (DelegationSurrogate surrogate)
{
surrogate = _surrogates[delegatee];
if (address(surrogate) != address(0)) return surrogate;
bytes32 salt = keccak256(abi.encode(delegatee));
surrogate = new DelegationSurrogate{salt: salt}(VOTING_TOKEN, delegatee);
_surrogates[delegatee] = surrogate;
emit SurrogateDeployed(delegatee, address(surrogate));
return surrogate;
}API helpers. Use predictSurrogateAddress to derive CREATE2 outcomes off-chain and getDelegateeFromSurrogate to audit live delegates.
YieldSkimmingTokenizedStrategy
Goal: Maintain solvency-aware accounting when underlying assets appreciate via exchange rates (e.g., yield-bearing vault tokens) and skim the profit buffer (“dragon router”) on each report.
- Value debt ledgers: Track user debt and dragon router debt separately in value terms (1 share = 1 unit of asset value).
- Profit capture: Appreciation mints donation shares to the dragon router; losses burn dragon shares before touching user balances.
- Solvency gates: Deposits/withdrawals revert when the vault is insolvent; dragon transfers trigger debt rebalancing.
function report()
public
override(TokenizedStrategy)
nonReentrant
onlyKeepers
returns (uint256 profit, uint256 loss)
{
StrategyData storage S = _strategyStorage();
YieldSkimmingStorage storage YS = _strategyYieldSkimmingStorage();
uint256 currentRate = _currentRateRay();
uint256 totalValue = _totalAssetsValue(S, currentRate);
uint256 totalDebt = YS.totalUserDebtInAssetValue + YS.dragonRouterDebtInAssetValue;
if (totalValue >= totalDebt) {
profit = totalValue - totalDebt;
_mintDragonDonation(S, YS, profit, currentRate);
} else {
uint256 lossValue = totalDebt - totalValue;
loss = _handleDragonLossProtection(S, YS, lossValue, currentRate);
}
YS.lastReportedRate = currentRate;
emit Harvest(msg.sender, currentRate);
}Developer tips: Override _currentRateRay to surface protocol-specific rates and ensure reports run frequently enough for health-check thresholds to remain meaningful.