Beefy Finance Protocol Adapter

The Beefy Finance adapter enables the Yieldinator Facet to integrate with Beefy's multi-chain yield optimization vaults, allowing users to earn auto-compounded yields across multiple blockchain networks.

Overview

Beefy Finance is a decentralized, multi-chain yield optimizer platform that allows users to earn compound interest on their crypto holdings. The protocol automates the best yield farming opportunities across 15+ blockchain networks by leveraging different DeFi protocols and strategies. Beefy's vaults auto-harvest rewards and reinvest them to maximize returns while minimizing gas costs.

The Beefy adapter facilitates deposits into Beefy vaults, manages mooToken balances (Beefy's vault tokens), and handles cross-chain yield optimization through a standardized interface.

Implementation Details

Contract: BeefyAdapter.sol

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

contract BeefyAdapter is YieldinatorAdapter {
    using SafeERC20 for IERC20;
    
    // Vault configuration
    struct VaultConfig {
        address vault;        // Beefy vault address
        address mooToken;     // Vault's receipt token (mooToken)
        address want;         // Token the vault accepts (underlying asset)
        uint256 chainId;      // Chain ID where the vault is deployed
        bool active;          // Whether the vault is active
    }
    
    // Mapping from token to vault configuration
    mapping(address => VaultConfig) public tokenToVault;
    
    // Deposited amounts per token
    mapping(address => uint256) public depositedAmount;
    
    // Chain ID registry for cross-chain operations
    mapping(uint256 => bool) public supportedChainIds;
    
    // Cross-chain bridge contracts (if applicable)
    address public bridgeContract;
    
    // Constructor
    constructor(
        address _admin,
        address _bridgeContract
    ) YieldinatorAdapter("Beefy Finance", _admin) {
        require(_bridgeContract != address(0), "BeefyAdapter: bridge contract cannot be zero address");
        bridgeContract = _bridgeContract;
        
        // Add default supported chain (current chain)
        supportedChainIds[block.chainid] = true;
    }
}

Key Functions

Vault Registration

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

function registerVault(
    address _token,
    address _vault,
    address _mooToken,
    uint256 _chainId
) external onlyRole(ADMIN_ROLE) {
    require(_token != address(0), "BeefyAdapter: token cannot be zero address");
    require(_vault != address(0), "BeefyAdapter: vault cannot be zero address");
    require(_mooToken != address(0), "BeefyAdapter: mooToken cannot be zero address");
    require(supportedChainIds[_chainId], "BeefyAdapter: chain ID not supported");
    
    // Get the want token from the vault
    address want = IBeefyVault(_vault).want();
    require(want == _token, "BeefyAdapter: token must match vault's want token");
    
    // Create and store vault configuration
    VaultConfig memory config = VaultConfig({
        vault: _vault,
        mooToken: _mooToken,
        want: want,
        chainId: _chainId,
        active: true
    });
    
    tokenToVault[_token] = config;
    
    emit VaultRegistered(_token, _vault, _chainId);
}

function addSupportedChain(uint256 _chainId) external onlyRole(ADMIN_ROLE) {
    supportedChainIds[_chainId] = true;
    emit ChainSupported(_chainId);
}

function removeSupportedChain(uint256 _chainId) external onlyRole(ADMIN_ROLE) {
    supportedChainIds[_chainId] = false;
    emit ChainRemoved(_chainId);
}

Deposit

The deposit function handles adding funds to Beefy vaults, with special handling for cross-chain deposits:

function deposit(address _token, uint256 _amount)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (bool)
{
    require(_amount > 0, "BeefyAdapter: amount must be greater than 0");
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    
    // Transfer token from caller
    IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
    
    if (config.chainId == block.chainid) {
        // Same chain deposit
        return _depositSameChain(_token, _amount, config);
    } else {
        // Cross-chain deposit
        return _depositCrossChain(_token, _amount, config);
    }
}

function _depositSameChain(address _token, uint256 _amount, VaultConfig memory _config) 
    internal 
    returns (bool) 
{
    // Approve vault to use tokens
    IERC20(_token).safeApprove(_config.vault, _amount);
    
    // Get balance before deposit
    uint256 mooTokenBefore = IERC20(_config.mooToken).balanceOf(address(this));
    
    // Deposit into Beefy vault
    IBeefyVault(_config.vault).deposit(_amount);
    
    // Calculate received mooTokens
    uint256 mooTokenAfter = IERC20(_config.mooToken).balanceOf(address(this));
    uint256 mooTokenReceived = mooTokenAfter - mooTokenBefore;
    
    // Update deposited amount
    depositedAmount[_token] += _amount;
    
    emit Deposited(_token, _amount);
    emit MooTokensReceived(_token, mooTokenReceived);
    return true;
}

function _depositCrossChain(address _token, uint256 _amount, VaultConfig memory _config) 
    internal 
    returns (bool) 
{
    // Approve bridge to use tokens
    IERC20(_token).safeApprove(bridgeContract, _amount);
    
    // Bridge tokens to target chain
    IBridgeContract(bridgeContract).bridgeTokens(
        _token,
        _amount,
        _config.chainId,
        abi.encodeWithSignature("receiveCrossChainDeposit(address,uint256,address)", _token, _amount, msg.sender)
    );
    
    // Update deposited amount
    depositedAmount[_token] += _amount;
    
    emit CrossChainDepositInitiated(_token, _amount, _config.chainId);
    return true;
}

function receiveCrossChainDeposit(address _token, uint256 _amount, address _originator) 
    external 
    onlyBridge 
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    require(config.chainId == block.chainid, "BeefyAdapter: wrong chain for vault");
    
    // Approve vault to use tokens
    IERC20(_token).safeApprove(config.vault, _amount);
    
    // Deposit into Beefy vault
    IBeefyVault(config.vault).deposit(_amount);
    
    emit CrossChainDepositCompleted(_token, _amount, _originator);
}

Withdraw

The withdraw function handles removing funds from Beefy vaults, with special handling for cross-chain withdrawals:

function withdraw(address _token, uint256 _amount)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    require(_amount > 0, "BeefyAdapter: amount must be greater than 0");
    require(_amount <= depositedAmount[_token], "BeefyAdapter: insufficient balance");
    
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    
    if (config.chainId == block.chainid) {
        // Same chain withdrawal
        return _withdrawSameChain(_token, _amount, config);
    } else {
        // Cross-chain withdrawal
        return _withdrawCrossChain(_token, _amount, config);
    }
}

function _withdrawSameChain(address _token, uint256 _amount, VaultConfig memory _config) 
    internal 
    returns (uint256) 
{
    // Calculate proportion of mooTokens to withdraw
    uint256 mooTokenBalance = IERC20(_config.mooToken).balanceOf(address(this));
    uint256 vaultBalance = IBeefyVault(_config.vault).balance();
    uint256 totalSupply = IBeefyVault(_config.vault).totalSupply();
    
    // Calculate shares to withdraw
    uint256 sharesToWithdraw = (_amount * totalSupply) / vaultBalance;
    require(sharesToWithdraw <= mooTokenBalance, "BeefyAdapter: insufficient mooToken balance");
    
    // Withdraw from Beefy vault
    IBeefyVault(_config.vault).withdraw(sharesToWithdraw);
    
    // Get actual withdrawn amount
    uint256 withdrawnAmount = IERC20(_token).balanceOf(address(this));
    require(withdrawnAmount >= _amount * 95 / 100, "BeefyAdapter: withdrawal amount too low");
    
    // Update deposited amount
    depositedAmount[_token] -= _amount;
    
    // Transfer withdrawn token to caller
    IERC20(_token).safeTransfer(msg.sender, withdrawnAmount);
    
    emit Withdrawn(_token, withdrawnAmount);
    return withdrawnAmount;
}

function _withdrawCrossChain(address _token, uint256 _amount, VaultConfig memory _config) 
    internal 
    returns (uint256) 
{
    // Initiate cross-chain withdrawal request
    IBridgeContract(bridgeContract).requestCrossChainWithdrawal(
        _token,
        _amount,
        _config.chainId,
        abi.encodeWithSignature("processCrossChainWithdrawal(address,uint256,address)", _token, _amount, msg.sender)
    );
    
    // Update deposited amount
    depositedAmount[_token] -= _amount;
    
    emit CrossChainWithdrawalInitiated(_token, _amount, _config.chainId);
    
    // Return 0 as the actual withdrawal happens asynchronously
    return 0;
}

function processCrossChainWithdrawal(address _token, uint256 _amount, address _recipient) 
    external 
    onlyBridge 
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    require(config.chainId == block.chainid, "BeefyAdapter: wrong chain for vault");
    
    // Calculate proportion of mooTokens to withdraw
    uint256 mooTokenBalance = IERC20(config.mooToken).balanceOf(address(this));
    uint256 vaultBalance = IBeefyVault(config.vault).balance();
    uint256 totalSupply = IBeefyVault(config.vault).totalSupply();
    
    // Calculate shares to withdraw
    uint256 sharesToWithdraw = (_amount * totalSupply) / vaultBalance;
    require(sharesToWithdraw <= mooTokenBalance, "BeefyAdapter: insufficient mooToken balance");
    
    // Withdraw from Beefy vault
    IBeefyVault(config.vault).withdraw(sharesToWithdraw);
    
    // Get actual withdrawn amount
    uint256 withdrawnAmount = IERC20(_token).balanceOf(address(this));
    
    // Bridge tokens back to origin chain
    IERC20(_token).safeApprove(bridgeContract, withdrawnAmount);
    IBridgeContract(bridgeContract).bridgeTokens(
        _token,
        withdrawnAmount,
        block.chainid, // Origin chain
        abi.encodeWithSignature("receiveCrossChainWithdrawal(address,uint256,address)", _token, withdrawnAmount, _recipient)
    );
    
    emit CrossChainWithdrawalProcessed(_token, withdrawnAmount, _recipient);
}

function receiveCrossChainWithdrawal(address _token, uint256 _amount, address _recipient) 
    external 
    onlyBridge 
{
    // Transfer tokens to recipient
    IERC20(_token).safeTransfer(_recipient, _amount);
    
    emit CrossChainWithdrawalCompleted(_token, _amount, _recipient);
}

Harvest Yield

The harvestYield function collects auto-compounded yields from Beefy vaults:

function harvestYield(address _token)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    
    if (config.chainId != block.chainid) {
        // Cross-chain harvesting not supported directly
        return 0;
    }
    
    // Calculate current value of mooTokens
    uint256 mooTokenBalance = IERC20(config.mooToken).balanceOf(address(this));
    uint256 vaultBalance = IBeefyVault(config.vault).balance();
    uint256 totalSupply = IBeefyVault(config.vault).totalSupply();
    
    // Calculate current value in want tokens
    uint256 currentValue = (mooTokenBalance * vaultBalance) / totalSupply;
    
    // Calculate yield as the difference between current value and deposited amount
    uint256 yieldAmount = 0;
    if (currentValue > depositedAmount[_token]) {
        yieldAmount = currentValue - depositedAmount[_token];
    }
    
    if (yieldAmount > 0) {
        // Calculate shares to withdraw for yield only
        uint256 sharesToWithdraw = (yieldAmount * totalSupply) / vaultBalance;
        
        // Withdraw yield portion from Beefy vault
        IBeefyVault(config.vault).withdraw(sharesToWithdraw);
        
        // Get actual withdrawn amount
        uint256 withdrawnAmount = IERC20(_token).balanceOf(address(this));
        
        // Transfer harvested yield to caller
        IERC20(_token).safeTransfer(msg.sender, withdrawnAmount);
        
        emit YieldHarvested(_token, withdrawnAmount);
        return withdrawnAmount;
    }
    
    return 0;
}

Emergency Withdraw

The emergencyWithdraw function provides a way to recover funds in case of emergencies:

function emergencyWithdraw(address _token)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    
    if (config.chainId != block.chainid) {
        // Initiate emergency cross-chain withdrawal
        IBridgeContract(bridgeContract).emergencyCrossChainWithdrawal(
            _token,
            config.chainId,
            abi.encodeWithSignature("processEmergencyCrossChainWithdrawal(address,address)", _token, msg.sender)
        );
        
        // Reset deposited amount
        depositedAmount[_token] = 0;
        
        emit EmergencyCrossChainWithdrawalInitiated(_token, config.chainId);
        return 0;
    }
    
    // Get all mooTokens
    uint256 mooTokenBalance = IERC20(config.mooToken).balanceOf(address(this));
    
    if (mooTokenBalance > 0) {
        // Withdraw all from Beefy vault
        IBeefyVault(config.vault).withdraw(mooTokenBalance);
    }
    
    // Get actual withdrawn amount
    uint256 withdrawnAmount = IERC20(_token).balanceOf(address(this));
    
    // Reset deposited amount
    depositedAmount[_token] = 0;
    
    // Transfer all tokens to caller
    if (withdrawnAmount > 0) {
        IERC20(_token).safeTransfer(msg.sender, withdrawnAmount);
    }
    
    emit EmergencyWithdrawn(_token, withdrawnAmount);
    return withdrawnAmount;
}

function processEmergencyCrossChainWithdrawal(address _token, address _recipient) 
    external 
    onlyBridge 
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BeefyAdapter: vault not active for token");
    require(config.chainId == block.chainid, "BeefyAdapter: wrong chain for vault");
    
    // Get all mooTokens
    uint256 mooTokenBalance = IERC20(config.mooToken).balanceOf(address(this));
    
    if (mooTokenBalance > 0) {
        // Withdraw all from Beefy vault
        IBeefyVault(config.vault).withdraw(mooTokenBalance);
    }
    
    // Get actual withdrawn amount
    uint256 withdrawnAmount = IERC20(_token).balanceOf(address(this));
    
    // Bridge tokens back to origin chain
    if (withdrawnAmount > 0) {
        IERC20(_token).safeApprove(bridgeContract, withdrawnAmount);
        IBridgeContract(bridgeContract).bridgeTokens(
            _token,
            withdrawnAmount,
            block.chainid, // Origin chain
            abi.encodeWithSignature("receiveEmergencyCrossChainWithdrawal(address,uint256,address)", _token, withdrawnAmount, _recipient)
        );
    }
    
    emit EmergencyCrossChainWithdrawalProcessed(_token, withdrawnAmount, _recipient);
}

function receiveEmergencyCrossChainWithdrawal(address _token, uint256 _amount, address _recipient) 
    external 
    onlyBridge 
{
    // Transfer tokens to recipient
    IERC20(_token).safeTransfer(_recipient, _amount);
    
    emit EmergencyCrossChainWithdrawalCompleted(_token, _amount, _recipient);
}

Usage Examples

Registering a Beefy Vault on Ethereum Mainnet

// Register a WETH vault on Ethereum mainnet
beefyAdapter.registerVault(
    WETH_ADDRESS,
    BEEFY_WETH_VAULT_ADDRESS,
    BEEFY_MOO_WETH_ADDRESS,
    1 // Ethereum chain ID
);

Registering a Beefy Vault on Polygon

// First add Polygon as a supported chain
beefyAdapter.addSupportedChain(137); // Polygon chain ID

// Register a WMATIC vault on Polygon
beefyAdapter.registerVault(
    WMATIC_ADDRESS,
    BEEFY_WMATIC_VAULT_ADDRESS,
    BEEFY_MOO_WMATIC_ADDRESS,
    137 // Polygon chain ID
);

Depositing into Beefy

// Deposit 1 ETH into the registered Ethereum vault
uint256 amount = 1 ether;
yieldinator.deposit(WETH_ADDRESS, amount, beefyAdapter);

// Deposit 100 MATIC into the registered Polygon vault (will be bridged)
uint256 maticAmount = 100 ether;
yieldinator.deposit(WMATIC_ADDRESS, maticAmount, beefyAdapter);

Withdrawing from Beefy

// Withdraw 0.5 ETH from the registered Ethereum vault
uint256 amount = 0.5 ether;
yieldinator.withdraw(WETH_ADDRESS, amount, beefyAdapter);

// Withdraw 50 MATIC from the registered Polygon vault (will be bridged back)
uint256 maticAmount = 50 ether;
yieldinator.withdraw(WMATIC_ADDRESS, maticAmount, beefyAdapter);

Harvesting Yield

// Harvest yield from the WETH vault
yieldinator.harvestYield(WETH_ADDRESS, beefyAdapter);

Security Considerations

Cross-Chain Risks

The multi-chain nature of Beefy Finance introduces additional security considerations:

  1. Bridge Security: The adapter relies on cross-chain bridges for non-native chain operations, which introduces counterparty risk. The adapter implements strict validation to ensure only authorized bridge contracts can call cross-chain callback functions.

  2. Asynchronous Operations: Cross-chain operations are inherently asynchronous, which can lead to temporary inconsistencies in reported balances. The adapter implements proper event emissions to track the status of cross-chain operations.

  3. Chain Outages: If a specific blockchain experiences downtime, funds on that chain may be temporarily inaccessible. The adapter includes emergency functions that can be triggered by governance to recover funds in such scenarios.

Auto-Compounding Strategy Risks

Beefy's auto-compounding strategies may interact with multiple protocols, inheriting risks from each:

  1. Strategy Upgrades: Beefy vaults may upgrade their underlying strategies, potentially changing risk profiles. The adapter includes functions to pause specific vaults if needed.

  2. Reward Token Volatility: Auto-compounding strategies sell reward tokens for the underlying asset, which can be affected by market volatility. The adapter focuses on the underlying token value rather than intermediate reward tokens.

Slippage and MEV Protection

When withdrawing from Beefy vaults, the adapter implements slippage protection to ensure users receive a fair amount of tokens:

  1. Minimum Output Validation: The adapter verifies that withdrawals return at least 95% of the expected amount to protect against extreme slippage.

  2. MEV Protection: The adapter uses direct vault interactions rather than routing through DEXes to minimize MEV extraction opportunities.

Conclusion

The Beefy Finance adapter provides comprehensive integration with Beefy's multi-chain yield optimization platform, allowing the Yieldinator Facet to leverage auto-compounding strategies across multiple blockchains. The adapter's cross-chain capabilities significantly expand the yield opportunities available to users, while its security measures ensure robust protection against the additional risks introduced by multi-chain operations.