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
modifierCommon Asset Interface: Standardized
receiveAsset
andclaimAsset
functionsAddress 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:
Inherit from BaseHandler:
contract MyHandler is BaseHandler, IMyHandler
Implement Core Methods: Override
receiveAsset
andclaimAsset
Use Standardized Modifiers: Apply access control and validation consistently
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
Inherit BaseHandler: All handlers must extend
BaseHandler
Implement Required Methods: Override
receiveAsset
andclaimAsset
Use Standard Modifiers: Apply
onlyStrategyAllocator
,validAddress
,validAmount
Follow Naming Conventions: Use underscore suffix for parameters (
amount_
,to_
)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