Strategy Modules

Overview

Ocean Finance implements a modular strategy architecture through specialized handler contracts built on a standardized abstract base. Each handler manages interactions with external yield protocols, allowing the protocol to diversify yield sources while maintaining security, operational efficiency, and consistent interfaces.

Handler Architecture

BaseHandler Abstract Contract

Contract: BaseHandler.sol Purpose: Provides standardized interface and common functionality for all strategy handlers

Core Interface

abstract contract BaseHandler is IBaseHandler {
    IAddressProvider public addressProvider;
    
    constructor(IAddressProvider addressProvider_) {
        addressProvider = addressProvider_;
    }
    
    // Standard asset management interface
    function receiveAsset(address asset_, uint256 amount_) external virtual;
    function claimAsset(address to_, address asset_, uint256 amount_) external virtual;
    
    // Access control modifiers
    modifier onlyStrategyAllocator() {
        require(msg.sender == addressProvider.strategyAllocator(), "Not strategy allocator");
        _;
    }
    
    modifier validAddress(address address_) {
        require(address_ != address(0), "Invalid address");
        _;
    }
    
    modifier validAmount(uint256 amount_) {
        require(amount_ > 0, "Invalid amount");
        _;
    }
}

Standardized Features

  • Unified Access Control: All handlers use onlyStrategyAllocator modifier

  • Common Asset Interface: Standardized receiveAsset and claimAsset functions

  • Address Resolution: Centralized address management via AddressProvider

  • Input Validation: Common validation modifiers for addresses and amounts

  • Event Standardization: Consistent event patterns through IBaseHandlerEvents

Implementation Requirements

All concrete handlers must:

  1. Inherit from BaseHandler: contract MyHandler is BaseHandler, IMyHandler

  2. Implement Core Methods: Override receiveAsset and claimAsset

  3. Use Standardized Modifiers: Apply access control and validation consistently

  4. Follow Event Patterns: Emit standardized events for tracking

Handler Inheritance Hierarchy

EthenaHandler

Contract: EthenaHandler.sol Inherits: BaseHandler, IEthenaHandler Purpose: Manages interactions with Ethena Protocol for USDe and sUSDe

Implementation

contract EthenaHandler is BaseHandler, IEthenaHandler {
    constructor(IAddressProvider addressProvider_) 
        BaseHandler(addressProvider_) {}
        
    function receiveAsset(address asset_, uint256 amount_) 
        external override onlyStrategyAllocator validAmount(amount_) {
        // Standardized asset receipt implementation
    }
}

Core Operations

Staking USDe

function stakeUSDe(uint256 amount_, uint256 minShares_) 
    external onlyStrategyAllocator validAmount(amount_) {
    // Stakes USDe for sUSDe to earn delta-neutral yield
    IStakedUSDe(sUSDe).deposit(amount_, address(this));
}

Cooldown Management

function cooldownAssets(uint256 assets_) 
    external onlyStrategyAllocator validAmount(assets_) returns (uint256 shares) {
    // Initiates Ethena cooldown for sUSDe unstaking
    return IStakedUSDe(sUSDe).cooldownAssets(assets_);
}

function cooldownShares(uint256 shares_) 
    external onlyStrategyAllocator validAmount(shares_) returns (uint256 assets) {
    // Alternative cooldown initiation by shares
    return IStakedUSDe(sUSDe).cooldownShares(shares_);
}

Unstaking Operations

function unstakeUSDe(address receiver_) 
    external onlyStrategyAllocator validAddress(receiver_) returns (uint256 assets) {
    // Claims assets after cooldown period expires
    return IStakedUSDe(sUSDe).unstake(receiver_);
}

function withdrawUSDe(uint256 assets_, address receiver_) 
    external onlyStrategyAllocator validAmount(assets_) validAddress(receiver_) 
    returns (uint256 shares) {
    // Direct withdrawal if cooldown is disabled
    return IStakedUSDe(sUSDe).withdraw(assets_, receiver_, address(this));
}

function redeemUSDe(uint256 shares_, address receiver_) 
    external onlyStrategyAllocator validAmount(shares_) validAddress(receiver_) 
    returns (uint256 assets) {
    // Direct redemption if cooldown is disabled  
    return IStakedUSDe(sUSDe).redeem(shares_, receiver_, address(this));
}

Asset Management

function transferAsset(address asset, address to, uint256 amount) external onlyAdmin {
    // Emergency asset transfer capability
    IERC20(asset).safeTransfer(to, amount);
}

function approveAsset(address asset, address spender, uint256 amount) external onlyService {
    // Manage approvals for protocol interactions
    IERC20(asset).forceApprove(spender, amount);
}

Key Features

  • Delta-Neutral Yield: Ethena's sUSDe provides yield without directional crypto exposure

  • Cooldown Management: Handles Ethena's unstaking cooldown periods

  • Flexible Withdrawals: Supports both cooldown and direct withdrawal modes

  • Balance Tracking: Maintains accurate asset accounting

SkyHandler

Contract: SkyHandler.sol Inherits: BaseHandler, ISkyHandler Purpose: Manages interactions with Sky Protocol for USDS and sUSDS

Implementation

contract SkyHandler is BaseHandler, ISkyHandler {
    constructor(IAddressProvider addressProvider_) 
        BaseHandler(addressProvider_) {}
        
    function receiveAsset(address asset_, uint256 amount_) 
        external override onlyStrategyAllocator validAmount(amount_) {
        // Standardized asset receipt with optional auto-staking
    }
}

Core Operations

Staking USDS

function stakeUsds(uint256 amount_) 
    external onlyStrategyAllocator validAmount(amount_) {
    // Stakes USDS for sUSDS to earn Sky rewards
    ISavingsUsds(sUSDS).deposit(amount_, address(this));
}

Unstaking Operations

function withdrawUsds(uint256 assets_, address receiver_) 
    external onlyStrategyAllocator validAmount(assets_) validAddress(receiver_) 
    returns (uint256 shares) {
    // Withdraw specific amount of USDS
    return ISavingsUsds(sUSDS).withdraw(assets_, receiver_, address(this));
}

function redeemUsds(uint256 shares_, address receiver_) 
    external onlyStrategyAllocator validAmount(shares_) validAddress(receiver_) 
    returns (uint256 assets) {
    // Redeem specific shares for USDS
    return ISavingsUsds(sUSDS).redeem(shares_, receiver_, address(this));
}

PSM Swapping

function swapUsdcToUsds(uint256 amountIn_) 
    external onlyStrategyAllocator validAmount(amountIn_) returns (uint256 amountOut) {
    // Convert USDC to USDS via PSM
    amountOut = IPsm(psm).sellGem(address(this), amountIn_);
}

function swapUsdsToUsdc(uint256 amountIn_) 
    external onlyStrategyAllocator validAmount(amountIn_) returns (uint256 amountOut) {
    // Convert USDS to USDC via PSM  
    amountOut = IPsm(psm).buyGem(address(this), amountIn_);
}

Balance Tracking

function getUsdsBalance() external view returns (uint256) {
    // Get USDS balance including staked positions
    return IERC20(usds).balanceOf(address(this)) + 
           ISavingsUsds(sUSDS).maxWithdraw(address(this));
}

function getSUsdsBalance() external view returns (uint256) {
    // Get sUSDS share balance
    return IERC20(sUSDS).balanceOf(address(this));
}

Key Features

  • Sky Yield: Earns yield through Sky Protocol's savings rate

  • PSM Integration: Efficient USDC/USDS swapping via Peg Stability Module

  • ERC4626 Compliance: Standard vault interface for staking operations

  • Balance Aggregation: Comprehensive balance tracking across staked/unstaked positions

NestHandler

Contract: NestHandler.sol Inherits: BaseHandler, INestHandler Purpose: Manages deposits and withdrawals from Nest Protocol vaults

Implementation

contract NestHandler is BaseHandler, INestHandler {
    using EnumerableSet for EnumerableSet.AddressSet;
    
    constructor(IAddressProvider addressProvider_) 
        BaseHandler(addressProvider_) {}
        
    function receiveAsset(address asset_, uint256 amount_) 
        external override onlyStrategyAllocator validAmount(amount_) {
        // Standardized asset receipt with vault management
    }
}

Core Operations

Vault Deposits

function deposit(
    address teller_,
    address vault_, 
    uint256 amount_,
    uint256 minimumMint_
) external onlyStrategyAllocator validAddress(teller_) validAddress(vault_) 
    validAmount(amount_) returns (uint256 shares) {
    // Deposit assets into Nest vault via Teller
    shares = ITeller(teller_).deposit(vault_, asset, amount_, minimumMint_);
}

Atomic Withdrawals

function withdraw(
    address atomicQueue_,
    address vault_,
    uint256 shares_,
    address receiver_
) external onlyStrategyAllocator validAddress(atomicQueue_) validAddress(vault_) 
    validAmount(shares_) validAddress(receiver_) returns (uint256 assets) {
    // Create atomic withdrawal request
    assets = IAtomicQueue(atomicQueue_).updateAtomicRequest(
        vault_, shares_, receiver_, address(this)
    );
}

Teller Management

function addValidTeller(address teller) external onlyAdmin {
    // Add authorized teller for deposits
    validTellers.add(teller);
}

function removeValidTeller(address teller) external onlyAdmin {
    // Remove teller authorization
    validTellers.remove(teller);
}

function isValidTeller(address teller) external view returns (bool) {
    // Check if teller is authorized
    return validTellers.contains(teller);
}

Balance Tracking

function getVaultBalance(address vault) external view returns (uint256) {
    // Get balance in specific Nest vault
    return IERC20(vault).balanceOf(address(this));
}

function getTotalBalance() external view returns (uint256 total) {
    // Aggregate balance across all vaults
    uint256 length = validVaults.length();
    for (uint256 i = 0; i < length; i++) {
        total += IERC20(validVaults.at(i)).balanceOf(address(this));
    }
}

Key Features

  • Multi-Vault Support: Can interact with multiple Nest vaults simultaneously

  • Teller Validation: Only authorized tellers can be used for deposits

  • Atomic Withdrawals: Efficient withdrawal mechanism via AtomicQueue

  • Vault Enumeration: Tracks and manages multiple vault positions

Handler Security Model

Standardized Access Control

All handlers inherit consistent access control from BaseHandler:

// Primary access control - only StrategyAllocator can execute operations
modifier onlyStrategyAllocator() {
    require(msg.sender == addressProvider.strategyAllocator(), "Not strategy allocator");
    _;
}

// Input validation modifiers
modifier validAddress(address address_) {
    require(address_ != address(0), "Invalid address");
    _;
}

modifier validAmount(uint256 amount_) {
    require(amount_ > 0, "Invalid amount");
    _;
}

Operation Validation

  • Asset Verification: All operations validate asset addresses

  • Amount Validation: Prevent zero-amount operations

  • Slippage Protection: Minimum output amounts where applicable

  • Reentrancy Guards: Protect against reentrancy attacks

Emergency Functions

function emergencyWithdraw(address asset, uint256 amount) external onlyAdmin {
    // Emergency asset recovery
    IERC20(asset).safeTransfer(msg.sender, amount);
}

function pause() external onlyAdmin {
    // Pause handler operations
    _pause();
}

Strategy Execution Flow

BaseHandler Standardization Benefits

Handler Integration Pattern

1. Whitelisting

  • Handlers must be whitelisted in StrategyAllocator

  • Only SERVICE_ROLE can execute handler operations

  • Admin can add/remove handlers as needed

2. Asset Flow

User Collateral → MintingManager → StrategyAllocator → Handler → External Protocol

3. Yield Flow

External Protocol → Handler → StrategyAllocator → YieldDistributor → Users/Stakers

4. Reporting

  • Handlers report balances to StrategyAllocator

  • Real-time position tracking across all strategies

  • Automated rebalancing based on performance metrics

Handler Development Guide

Creating New Handlers

With the BaseHandler architecture, developing new handlers follows a standardized pattern:

1. Contract Structure

contract NewProtocolHandler is BaseHandler, INewProtocolHandler {
    constructor(IAddressProvider addressProvider_) 
        BaseHandler(addressProvider_) {}
    
    // Required implementations
    function receiveAsset(address asset_, uint256 amount_) 
        external override onlyStrategyAllocator validAmount(amount_) {
        // Handle incoming assets
    }
    
    function claimAsset(address to_, address asset_, uint256 amount_) 
        external override onlyStrategyAllocator validAddress(to_) validAmount(amount_) {
        // Handle outgoing assets
    }
    
    // Protocol-specific functions
    function protocolSpecificOperation(uint256 amount_) 
        external onlyStrategyAllocator validAmount(amount_) {
        // Custom protocol interactions
    }
}

2. Implementation Requirements

  1. Inherit BaseHandler: All handlers must extend BaseHandler

  2. Implement Required Methods: Override receiveAsset and claimAsset

  3. Use Standard Modifiers: Apply onlyStrategyAllocator, validAddress, validAmount

  4. Follow Naming Conventions: Use underscore suffix for parameters (amount_, to_)

  5. Emit Standard Events: Use IBaseHandlerEvents for consistency

3. Integration Checklist

Supported Protocol Types

  • Staking Protocols: For yield-bearing token strategies

  • Lending Protocols: For supply-side yield generation

  • AMM Protocols: For liquidity provision strategies

  • Vault Protocols: For complex multi-strategy yield

  • Bridge Protocols: For cross-chain yield opportunities

The BaseHandler architecture ensures that Ocean Finance can efficiently integrate with new protocols while maintaining security, consistency, and operational efficiency across all yield strategies.

Last updated