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
BeefyAdapter.solThe 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:
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.
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.
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:
Strategy Upgrades: Beefy vaults may upgrade their underlying strategies, potentially changing risk profiles. The adapter includes functions to pause specific vaults if needed.
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:
Minimum Output Validation: The adapter verifies that withdrawals return at least 95% of the expected amount to protect against extreme slippage.
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.