Curve Protocol Adapter

The Curve adapter enables the Yieldinator Facet to integrate with Curve Finance's liquidity pools and gauges, allowing vaults to earn trading fees and CRV rewards by providing liquidity to Curve's stablecoin and crypto pools.

Overview

Curve Finance is a decentralized exchange optimized for low-slippage swaps between assets that should be priced similarly (e.g., stablecoins or wrapped versions of the same asset). The Curve adapter facilitates deposits into Curve pools, manages LP token balances, stakes in Curve gauges, and harvests yield from trading fees and CRV rewards.

Implementation Details

Contract: CurveAdapter.sol

The adapter implements the standard YieldinatorAdapter interface with Curve-specific functionality:

contract CurveAdapter is YieldinatorAdapter {
    // Curve contracts
    address public crvToken;
    ICurveMinter public curveMinter;
    
    // Pool configuration
    struct PoolConfig {
        ICurvePool pool;
        ICurveGauge gauge;
        uint8 poolSize; // Number of tokens in the pool (2, 3, or 4)
        int128 tokenIndex; // Index of the token in the pool
        address lpToken; // LP token address
        bool useGauge; // Whether to stake in gauge
        bool active; // Whether the pool is active
    }
    
    // Mapping from token to pool configuration
    mapping(address => PoolConfig) public tokenToPool;
    
    // Deposited amounts per token
    mapping(address => uint256) public depositedAmount;
    
    // Constructor and core functions...
}

Key Functions

Pool Registration

Before using the adapter for a specific token, the pool must be registered:

function registerPool(
    address _token,
    address _pool,
    address _gauge,
    uint8 _poolSize,
    int128 _tokenIndex,
    address _lpToken,
    bool _useGauge
) external onlyRole(ADAPTER_ADMIN_ROLE)

Deposit

Deposits tokens into a Curve pool and optionally stakes LP tokens in the gauge:

function deposit(address _token, uint256 _amount) external override onlyRole(YIELDINATOR_ROLE) nonReentrant returns (bool)

The function:

  1. Transfers tokens from the caller to the adapter

  2. Prepares an amounts array based on the pool size (2, 3, or 4 tokens)

  3. Adds liquidity to the Curve pool

  4. If using a gauge, stakes the LP tokens in the gauge

  5. Updates the deposited amount tracking

Withdraw

Withdraws tokens from a Curve pool:

function withdraw(address _token, uint256 _amount) external override onlyRole(YIELDINATOR_ROLE) nonReentrant returns (bool)

The function:

  1. Calculates how many LP tokens to withdraw based on the virtual price

  2. If using a gauge, withdraws LP tokens from the gauge

  3. Removes liquidity from the pool, receiving the underlying token

  4. Transfers tokens to the caller

  5. Updates the deposited amount tracking

Harvest Yield

Harvests CRV rewards and trading fees:

function harvestYield(address _token) external override onlyRole(YIELDINATOR_ROLE) nonReentrant returns (uint256)

The function:

  1. If using a gauge, mints CRV rewards via the CurveMinter

  2. Claims any additional rewards from the gauge

  3. Transfers all reward tokens to the caller

Emergency Withdraw

Provides emergency withdrawal functionality:

function emergencyWithdraw(address _token) external override onlyRole(YIELDINATOR_ROLE) nonReentrant returns (uint256)

APY Calculation

Returns the current APY for a token in Curve:

function getCurrentAPY(address _token) external view override returns (uint256)

Note: In a production environment, this would require external data sources to calculate accurately.

Additional Functions

The adapter includes additional Curve-specific functions:

function getClaimableCRV(address _token) external view returns (uint256)

Usage Example

// Initialize the adapter
CurveAdapter curveAdapter = new CurveAdapter(crvTokenAddress, curveMinterAddress, adminAddress);

// Register a pool
curveAdapter.registerPool(
    daiAddress,
    triPoolAddress,
    triPoolGaugeAddress,
    3,  // 3-token pool
    0,  // DAI is at index 0
    triPoolLpTokenAddress,
    true  // Use gauge for staking
);

// Deposit tokens
curveAdapter.deposit(daiAddress, 1000 * 1e18);

// After some time, harvest yield
uint256 harvestedYield = curveAdapter.harvestYield(daiAddress);

// Withdraw tokens
curveAdapter.withdraw(daiAddress, 500 * 1e18);

// In case of emergency
curveAdapter.emergencyWithdraw(daiAddress);

Security Considerations

  • The adapter uses OpenZeppelin's SafeERC20 for token transfers to prevent common ERC20 vulnerabilities.

  • Non-reentrancy guards protect against reentrancy attacks during external calls.

  • Role-based access control ensures only authorized addresses can call sensitive functions.

  • The adapter validates pool configurations to prevent incorrect setups.

Gas Optimization

  • The adapter supports different pool sizes (2, 3, or 4 tokens) to optimize gas usage for each pool type.

  • LP tokens are staked in gauges only if the useGauge flag is set, allowing for gas savings when gauges are not needed.

  • The adapter includes a try/catch pattern for iterating through reward tokens to handle potential failures gracefully.

Integration Requirements

To use the Curve adapter, the following components are required:

  1. The CRV token address

  2. The Curve Minter contract address

  3. Curve pool addresses for the target assets

  4. Curve gauge addresses (if using gauges)

  5. LP token addresses for each pool

  6. Proper role assignments for the Yieldinator Facet

Supported Pool Types

The adapter supports various Curve pool types:

  • 2-token pools (e.g., USDC/USDT)

  • 3-token pools (e.g., DAI/USDC/USDT)

  • 4-token pools (e.g., sUSD/DAI/USDC/USDT)

Each pool requires specific configuration parameters during registration.