Ethena Protocol Adapter

The Ethena adapter enables the Yieldinator Facet to integrate with Ethena's yield-bearing stablecoin protocol, allowing vaults to earn yield on stablecoins through USDe and staked USDe (sUSDe).

Overview

Ethena is a protocol that offers USDe, a yield-bearing stablecoin, and sUSDe, a staked version that generates yield from delta-neutral strategies. The protocol uses a combination of perpetual futures positions across centralized and decentralized exchanges to maintain the peg of USDe to USD while generating yield. The Ethena adapter facilitates minting USDe with collateral, staking USDe to receive sUSDe, and managing these positions to generate yield for the Vaultinator Protocol.

Implementation Details

Contract: EthenaAdapter.sol

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

contract EthenaAdapter is YieldinatorAdapter {
    using SafeERC20 for IERC20;
    
    // Ethena contracts
    address public usde; // USDe stablecoin address
    address public susde; // staked USDe (sUSDe) address
    IEthenaStaking public staking; // Ethena staking contract
    IEthenaMinter public minter; // Ethena minting contract
    
    // Supported collateral tokens for minting USDe
    mapping(address => bool) public supportedCollateral;
    
    // Staking configuration
    struct StakingConfig {
        bool autoStake; // Whether to automatically stake USDe
        bool active; // Whether staking is active
    }
    
    // Configuration for each token
    mapping(address => StakingConfig) public tokenConfig;
    
    // Deposited amounts per token
    mapping(address => uint256) public depositedAmount;
    
    // Constructor
    constructor(
        address _admin,
        address _usde,
        address _susde,
        address _staking,
        address _minter
    ) YieldinatorAdapter("Ethena", _admin) {
        require(_usde != address(0), "EthenaAdapter: USDe cannot be zero address");
        require(_susde != address(0), "EthenaAdapter: sUSDe cannot be zero address");
        require(_staking != address(0), "EthenaAdapter: staking cannot be zero address");
        require(_minter != address(0), "EthenaAdapter: minter cannot be zero address");
        
        usde = _usde;
        susde = _susde;
        staking = IEthenaStaking(_staking);
        minter = IEthenaMinter(_minter);
    }
}

Key Functions

Configure Collateral and Staking

Before using the adapter, collateral tokens must be registered and staking configured:

function addSupportedCollateral(address _token) external onlyRole(ADMIN_ROLE) {
    require(_token != address(0), "EthenaAdapter: token cannot be zero address");
    require(minter.isSupportedCollateral(_token), "EthenaAdapter: token not supported by Ethena");
    
    supportedCollateral[_token] = true;
    
    emit CollateralAdded(_token);
}

function configureStaking(address _token, bool _autoStake) external onlyRole(ADMIN_ROLE) {
    require(supportedCollateral[_token] || _token == usde, "EthenaAdapter: token not supported");
    
    tokenConfig[_token] = StakingConfig({
        autoStake: _autoStake,
        active: true
    });
    
    emit StakingConfigured(_token, _autoStake);
}

Deposit

The deposit function handles depositing collateral to mint USDe or staking USDe directly:

function deposit(address _token, uint256 _amount)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (bool)
{
    require(_amount > 0, "EthenaAdapter: amount must be greater than 0");
    require(supportedCollateral[_token] || _token == usde, "EthenaAdapter: token not supported");
    require(tokenConfig[_token].active, "EthenaAdapter: token not active");
    
    // Transfer token from caller
    IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
    
    if (_token == usde) {
        // If token is USDe, stake it if autoStake is enabled
        if (tokenConfig[_token].autoStake) {
            IERC20(usde).safeApprove(address(staking), _amount);
            staking.stake(_amount);
        }
    } else {
        // If token is collateral, mint USDe
        IERC20(_token).safeApprove(address(minter), _amount);
        
        // Calculate expected USDe amount based on collateral value
        uint256 expectedUsde = _getExpectedUsdeAmount(_token, _amount);
        
        // Mint USDe using collateral
        minter.mint(_token, _amount, expectedUsde, address(this));
        
        // If autoStake is enabled, stake the minted USDe
        if (tokenConfig[_token].autoStake) {
            uint256 usdeBalance = IERC20(usde).balanceOf(address(this));
            IERC20(usde).safeApprove(address(staking), usdeBalance);
            staking.stake(usdeBalance);
        }
    }
    
    // Update deposited amount
    depositedAmount[_token] += _amount;
    
    emit Deposited(_token, _amount);
    return true;
}

function _getExpectedUsdeAmount(address _token, uint256 _amount) internal view returns (uint256) {
    // In a real implementation, this would query Ethena's oracle for the exact conversion rate
    // For this example, we'll use a simplified calculation
    
    if (_token == address(0)) {
        // ETH collateral
        return _amount * minter.getEthPrice() / 1e18;
    } else if (_isStablecoin(_token)) {
        // Stablecoin collateral (e.g., USDC, USDT)
        return _amount * 10**18 / 10**IERC20Metadata(_token).decimals();
    } else {
        // Other collateral (e.g., wBTC)
        return _amount * minter.getAssetPrice(_token) / 1e18;
    }
}

function _isStablecoin(address _token) internal pure returns (bool) {
    // Check if token is a common stablecoin
    // This is a simplified implementation
    return _token == USDC || _token == USDT || _token == DAI;
}

Withdraw

The withdraw function handles unstaking sUSDe and redeeming USDe for the original collateral:

function withdraw(address _token, uint256 _amount)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    require(_amount > 0, "EthenaAdapter: amount must be greater than 0");
    require(_amount <= depositedAmount[_token], "EthenaAdapter: insufficient balance");
    require(supportedCollateral[_token] || _token == usde, "EthenaAdapter: token not supported");
    require(tokenConfig[_token].active, "EthenaAdapter: token not active");
    
    uint256 amountToReturn;
    
    if (_token == usde) {
        // If token is USDe, unstake sUSDe if necessary
        if (tokenConfig[_token].autoStake) {
            // Calculate proportion of sUSDe to unstake
            uint256 sUsdeBalance = IERC20(susde).balanceOf(address(this));
            uint256 sUsdeToUnstake = sUsdeBalance * _amount / depositedAmount[_token];
            
            // Unstake sUSDe to get USDe
            staking.unstake(sUsdeToUnstake);
        }
        
        // Transfer USDe to caller
        uint256 usdeBalance = IERC20(usde).balanceOf(address(this));
        uint256 usdeToReturn = Math.min(_amount, usdeBalance);
        
        IERC20(usde).safeTransfer(msg.sender, usdeToReturn);
        amountToReturn = usdeToReturn;
    } else {
        // If token is collateral, we need to redeem USDe for the original collateral
        
        // Calculate proportion of USDe/sUSDe to redeem
        uint256 usdeNeeded = _getExpectedUsdeAmount(_token, _amount);
        
        // If USDe is staked, unstake first
        if (tokenConfig[_token].autoStake) {
            uint256 sUsdeBalance = IERC20(susde).balanceOf(address(this));
            
            // Calculate how much sUSDe we need to unstake to get the required USDe
            uint256 sUsdeToUnstake = _getSUsdeAmountForUsde(usdeNeeded);
            sUsdeToUnstake = Math.min(sUsdeToUnstake, sUsdeBalance);
            
            // Unstake sUSDe to get USDe
            staking.unstake(sUsdeToUnstake);
        }
        
        // Redeem USDe for original collateral
        uint256 usdeBalance = IERC20(usde).balanceOf(address(this));
        usdeNeeded = Math.min(usdeNeeded, usdeBalance);
        
        IERC20(usde).safeApprove(address(minter), usdeNeeded);
        
        uint256 collateralBefore = IERC20(_token).balanceOf(address(this));
        minter.redeem(_token, usdeNeeded, 0); // 0 for min amount, in production use slippage protection
        uint256 collateralAfter = IERC20(_token).balanceOf(address(this));
        
        // Calculate collateral received
        uint256 collateralReceived = collateralAfter - collateralBefore;
        
        // Transfer collateral to caller
        IERC20(_token).safeTransfer(msg.sender, collateralReceived);
        amountToReturn = collateralReceived;
    }
    
    // Update deposited amount
    depositedAmount[_token] -= _amount;
    
    emit Withdrawn(_token, amountToReturn);
    return amountToReturn;
}

function _getSUsdeAmountForUsde(uint256 _usdeAmount) internal view returns (uint256) {
    // Calculate how much sUSDe is needed to get the specified USDe amount
    // This uses the current exchange rate between sUSDe and USDe
    uint256 exchangeRate = staking.convertToAssets(1e18); // 1 sUSDe to USDe rate
    return _usdeAmount * 1e18 / exchangeRate;
}

Harvest Yield

The harvestYield function calculates and claims the yield earned from staking USDe:

function harvestYield(address _token)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    require(supportedCollateral[_token] || _token == usde, "EthenaAdapter: token not supported");
    require(tokenConfig[_token].active, "EthenaAdapter: token not active");
    
    uint256 yieldHarvested = 0;
    
    if (tokenConfig[_token].autoStake) {
        // Calculate yield as the increase in USDe value of sUSDe holdings
        uint256 sUsdeBalance = IERC20(susde).balanceOf(address(this));
        if (sUsdeBalance > 0) {
            // Current value in USDe
            uint256 currentUsdeValue = staking.convertToAssets(sUsdeBalance);
            
            // Original deposit value
            uint256 originalValue = depositedAmount[_token];
            if (_token != usde) {
                originalValue = _getExpectedUsdeAmount(_token, depositedAmount[_token]);
            }
            
            // Calculate yield
            if (currentUsdeValue > originalValue) {
                yieldHarvested = currentUsdeValue - originalValue;
                
                // Unstake the yield portion
                uint256 sUsdeToUnstake = _getSUsdeAmountForUsde(yieldHarvested);
                staking.unstake(sUsdeToUnstake);
                
                // Transfer yield to caller
                IERC20(usde).safeTransfer(msg.sender, yieldHarvested);
            }
        }
    } else if (_token == usde) {
        // For unstaked USDe, there's no yield to harvest
        yieldHarvested = 0;
    } else {
        // For collateral with minted USDe (not staked), check if USDe balance increased
        uint256 usdeBalance = IERC20(usde).balanceOf(address(this));
        uint256 expectedUsde = _getExpectedUsdeAmount(_token, depositedAmount[_token]);
        
        if (usdeBalance > expectedUsde) {
            yieldHarvested = usdeBalance - expectedUsde;
            
            // Transfer yield to caller
            IERC20(usde).safeTransfer(msg.sender, yieldHarvested);
        }
    }
    
    emit YieldHarvested(_token, 0, usde, yieldHarvested);
    return yieldHarvested;
}

Emergency Withdraw

The emergencyWithdraw function provides a safety mechanism to withdraw all funds:

function emergencyWithdraw(address _token)
    external
    override
    onlyRole(ADMIN_ROLE)
    nonReentrant
    returns (uint256)
{
    require(supportedCollateral[_token] || _token == usde, "EthenaAdapter: token not supported");
    
    uint256 amountReturned = 0;
    
    // If using staked USDe, unstake all sUSDe
    if (tokenConfig[_token].autoStake) {
        uint256 sUsdeBalance = IERC20(susde).balanceOf(address(this));
        if (sUsdeBalance > 0) {
            staking.unstake(sUsdeBalance);
        }
    }
    
    // If token is USDe, simply transfer all USDe to caller
    if (_token == usde) {
        uint256 usdeBalance = IERC20(usde).balanceOf(address(this));
        if (usdeBalance > 0) {
            IERC20(usde).safeTransfer(msg.sender, usdeBalance);
            amountReturned = usdeBalance;
        }
    } else {
        // If token is collateral, redeem all USDe for original collateral
        uint256 usdeBalance = IERC20(usde).balanceOf(address(this));
        if (usdeBalance > 0) {
            IERC20(usde).safeApprove(address(minter), usdeBalance);
            
            uint256 collateralBefore = IERC20(_token).balanceOf(address(this));
            minter.redeem(_token, usdeBalance, 0); // 0 for min amount, in production use slippage protection
            uint256 collateralAfter = IERC20(_token).balanceOf(address(this));
            
            // Calculate collateral received
            uint256 collateralReceived = collateralAfter - collateralBefore;
            
            // Add any existing collateral balance
            uint256 existingBalance = IERC20(_token).balanceOf(address(this));
            uint256 totalCollateral = collateralReceived + existingBalance;
            
            // Transfer all collateral to caller
            IERC20(_token).safeTransfer(msg.sender, totalCollateral);
            amountReturned = totalCollateral;
        }
    }
    
    // Reset deposited amount
    depositedAmount[_token] = 0;
    
    emit EmergencyWithdrawn(_token, amountReturned);
    return amountReturned;
}

APY Calculation

The getCurrentAPY function calculates the current APY for staking USDe:

function getCurrentAPY(address _token)
    external
    view
    override
    returns (uint256)
{
    require(supportedCollateral[_token] || _token == usde, "EthenaAdapter: token not supported");
    if (!tokenConfig[_token].active) return 0;
    
    // If autoStake is enabled, return the staking APY
    if (tokenConfig[_token].autoStake) {
        return _getEthenaStakingAPY();
    }
    
    // If token is USDe but not staked, return 0
    if (_token == usde) {
        return 0;
    }
    
    // For collateral with minted USDe (not staked), return 0
    return 0;
}

function _getEthenaStakingAPY() internal view returns (uint256) {
    // In a real implementation, this would query Ethena's API or contracts for the current APY
    // For this example, we'll return a fixed APY
    return 1500; // 15.00% APY
}

Get Total Deposited

The getTotalDeposited function returns the total amount of tokens deposited:

function getTotalDeposited(address _token)
    external
    view
    override
    returns (uint256)
{
    return depositedAmount[_token];
}

Usage Example

Here's an example of how to use the Ethena adapter with the Yieldinator Facet:

// Register the Ethena adapter with the Yieldinator Facet
address ethenaAdapter = address(new EthenaAdapter(
    admin,
    usde,
    susde,
    ethenaStaking,
    ethenaMinter
));
yieldinatorFacet.registerAdapter("Ethena", ethenaAdapter);

// Add supported collateral
EthenaAdapter(ethenaAdapter).addSupportedCollateral(USDC);
EthenaAdapter(ethenaAdapter).addSupportedCollateral(ETH);

// Configure staking for USDC and USDe
EthenaAdapter(ethenaAdapter).configureStaking(USDC, true); // Auto-stake USDe minted with USDC
EthenaAdapter(ethenaAdapter).configureStaking(usde, true); // Auto-stake USDe directly

// Add yield strategies using the Ethena adapter
yieldinatorFacet.addYieldStrategy(
    "Ethena USDC Staking",
    ethenaAdapter,
    USDC,
    1, // Low risk level
    1500 // 15.00% APY
);

yieldinatorFacet.addYieldStrategy(
    "Ethena USDe Staking",
    ethenaAdapter,
    usde,
    1, // Low risk level
    1500 // 15.00% APY
);

Security Considerations

  • Smart Contract Risk: The adapter interacts with multiple external contracts which may have vulnerabilities.

  • Oracle Risk: Ethena relies on price oracles for minting and redeeming USDe, which could be manipulated.

  • Delta-Neutral Strategy Risk: Ethena's yield generation relies on delta-neutral strategies that may be impacted by market conditions.

  • Stablecoin Peg Risk: USDe could potentially lose its peg to USD under extreme market conditions.

Risk Mitigation

  • The adapter implements strict access controls to prevent unauthorized access.

  • Emergency withdrawal functionality is available to recover funds in case of critical issues.

  • The adapter validates all inputs and handles edge cases to prevent unexpected behavior.

  • Slippage protection can be implemented for minting and redeeming operations.

Gas Optimization

  • The adapter minimizes gas usage by batching operations when possible.

  • The adapter avoids unnecessary approvals by only approving tokens when needed.

  • Auto-staking configuration allows for optimizing gas costs based on expected holding periods.

Future Improvements

  • Support for Ethena's governance system for participating in protocol decisions

  • Integration with Ethena's insurance mechanism if implemented

  • Support for leveraged staking strategies using USDe as collateral

  • Automated reinvestment of staking rewards for compound growth

  • Integration with other DeFi protocols that accept USDe or sUSDe as collateral