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
EthenaAdapter.solThe 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