Badger DAO Protocol Adapter

The Badger adapter enables the Yieldinator Facet to integrate with Badger DAO's yield-generating vaults, allowing users to earn yield on Bitcoin-related assets through Badger's Sett vaults and farming strategies.

Overview

Badger DAO is a decentralized autonomous organization focused on bringing Bitcoin to DeFi. The protocol offers Sett vaults (similar to Yearn vaults) that automatically compound yield from various farming strategies for BTC-related assets like WBTC, renBTC, and various tokenized Bitcoin derivatives. The Badger adapter facilitates deposits into these Sett vaults, manages vault positions, and harvests yield including BADGER token rewards.

Implementation Details

Contract: BadgerAdapter.sol

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

contract BadgerAdapter is YieldinatorAdapter {
    using SafeERC20 for IERC20;
    
    // Badger contracts
    address public badgerToken; // BADGER governance token
    address public bBadgerToken; // Staked BADGER token
    IBadgerSett public bBadgerSett; // BADGER staking Sett
    
    // Vault configuration
    struct VaultConfig {
        address sett; // Badger Sett (vault) address
        bool stakeBadger; // Whether to stake BADGER rewards
        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;
    
    // Constructor
    constructor(
        address _admin,
        address _badgerToken,
        address _bBadgerToken,
        address _bBadgerSett
    ) YieldinatorAdapter("Badger DAO", _admin) {
        require(_badgerToken != address(0), "BadgerAdapter: BADGER token cannot be zero address");
        require(_bBadgerToken != address(0), "BadgerAdapter: bBADGER token cannot be zero address");
        require(_bBadgerSett != address(0), "BadgerAdapter: bBADGER Sett cannot be zero address");
        
        badgerToken = _badgerToken;
        bBadgerToken = _bBadgerToken;
        bBadgerSett = IBadgerSett(_bBadgerSett);
    }
}

Key Functions

Vault Registration

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

function registerVault(
    address _token,
    address _sett,
    bool _stakeBadger
) external onlyRole(ADMIN_ROLE) {
    require(_token != address(0), "BadgerAdapter: token cannot be zero address");
    require(_sett != address(0), "BadgerAdapter: Sett cannot be zero address");
    
    // Verify the token is accepted by the Sett
    address settToken = IBadgerSett(_sett).token();
    require(settToken == _token, "BadgerAdapter: token mismatch with Sett");
    
    // Create and store vault configuration
    VaultConfig memory config = VaultConfig({
        sett: _sett,
        stakeBadger: _stakeBadger,
        active: true
    });
    
    tokenToVault[_token] = config;
    
    emit VaultRegistered(_token, _sett);
}

Deposit

The deposit function handles depositing assets into Badger Sett vaults:

function deposit(address _token, uint256 _amount)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (bool)
{
    require(_amount > 0, "BadgerAdapter: amount must be greater than 0");
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BadgerAdapter: vault not active for token");
    
    // Transfer token from caller
    IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
    
    // Approve Sett to use tokens
    IERC20(_token).safeApprove(config.sett, _amount);
    
    // Deposit into Badger Sett
    uint256 balanceBefore = IBadgerSett(config.sett).balanceOf(address(this));
    IBadgerSett(config.sett).deposit(_amount);
    uint256 balanceAfter = IBadgerSett(config.sett).balanceOf(address(this));
    
    // Calculate Sett tokens received
    uint256 settTokensReceived = balanceAfter - balanceBefore;
    require(settTokensReceived > 0, "BadgerAdapter: no Sett tokens received");
    
    // Update deposited amount
    depositedAmount[_token] += _amount;
    
    emit Deposited(_token, _amount);
    return true;
}

Withdraw

The withdraw function handles withdrawing assets from Badger Sett vaults:

function withdraw(address _token, uint256 _amount)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    require(_amount > 0, "BadgerAdapter: amount must be greater than 0");
    require(_amount <= depositedAmount[_token], "BadgerAdapter: insufficient balance");
    
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BadgerAdapter: vault not active for token");
    
    // Calculate proportion of Sett tokens to redeem
    uint256 settBalance = IBadgerSett(config.sett).balanceOf(address(this));
    uint256 settToRedeem = settBalance * _amount / depositedAmount[_token];
    
    // Withdraw from Badger Sett
    uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
    IBadgerSett(config.sett).withdraw(settToRedeem);
    uint256 balanceAfter = IERC20(_token).balanceOf(address(this));
    
    // Calculate amount received
    uint256 amountReceived = balanceAfter - balanceBefore;
    require(amountReceived > 0, "BadgerAdapter: no tokens received");
    
    // Transfer withdrawn tokens to caller
    IERC20(_token).safeTransfer(msg.sender, amountReceived);
    
    // Update deposited amount
    depositedAmount[_token] -= _amount;
    
    // Harvest any BADGER rewards
    _harvestBadgerRewards(config);
    
    emit Withdrawn(_token, amountReceived);
    return amountReceived;
}

Harvest Yield

The harvestYield function collects BADGER rewards and optionally stakes them:

function harvestYield(address _token)
    external
    override
    onlyRole(YIELDINATOR_ROLE)
    nonReentrant
    returns (uint256)
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BadgerAdapter: vault not active for token");
    
    // Claim BADGER rewards if available
    // Note: Some Badger Setts automatically distribute rewards, others require explicit claiming
    IBadgerSett sett = IBadgerSett(config.sett);
    if (address(sett) != address(0) && _canClaimRewards(sett)) {
        sett.claim();
    }
    
    return _harvestBadgerRewards(config);
}

function _harvestBadgerRewards(VaultConfig memory config) internal returns (uint256) {
    uint256 badgerBalance = IERC20(badgerToken).balanceOf(address(this));
    
    // If staking BADGER rewards is enabled, stake in bBADGER Sett
    if (config.stakeBadger && badgerBalance > 0) {
        IERC20(badgerToken).safeApprove(address(bBadgerSett), badgerBalance);
        bBadgerSett.deposit(badgerBalance);
        
        // Calculate bBADGER received
        uint256 bBadgerBalance = IERC20(bBadgerToken).balanceOf(address(this));
        emit YieldHarvested(badgerToken, badgerBalance, bBadgerToken, bBadgerBalance);
        return bBadgerBalance;
    } else if (badgerBalance > 0) {
        // Transfer BADGER rewards to caller
        IERC20(badgerToken).safeTransfer(msg.sender, badgerBalance);
        emit YieldHarvested(badgerToken, badgerBalance, address(0), 0);
        return badgerBalance;
    }
    
    return 0;
}

function _canClaimRewards(IBadgerSett sett) internal view returns (bool) {
    // Check if the Sett supports claiming rewards
    // Implementation depends on Badger's specific reward distribution mechanism
    try sett.canClaim(address(this)) returns (bool canClaim) {
        return canClaim;
    } catch {
        return false;
    }
}

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)
{
    VaultConfig memory config = tokenToVault[_token];
    require(config.active, "BadgerAdapter: vault not active for token");
    
    // Withdraw all Sett tokens from Badger Sett
    uint256 settBalance = IBadgerSett(config.sett).balanceOf(address(this));
    if (settBalance > 0) {
        IBadgerSett(config.sett).withdraw(settBalance);
    }
    
    // Transfer all token balance to caller
    uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
    if (tokenBalance > 0) {
        IERC20(_token).safeTransfer(msg.sender, tokenBalance);
    }
    
    // Transfer all BADGER and bBADGER to caller
    uint256 badgerBalance = IERC20(badgerToken).balanceOf(address(this));
    if (badgerBalance > 0) {
        IERC20(badgerToken).safeTransfer(msg.sender, badgerBalance);
    }
    
    uint256 bBadgerBalance = IERC20(bBadgerToken).balanceOf(address(this));
    if (bBadgerBalance > 0) {
        IERC20(bBadgerToken).safeTransfer(msg.sender, bBadgerBalance);
    }
    
    // Reset deposited amount
    depositedAmount[_token] = 0;
    
    emit EmergencyWithdrawn(_token, tokenBalance);
    return tokenBalance;
}

APY Calculation

The getCurrentAPY function calculates the current APY for a token in the Badger Sett:

function getCurrentAPY(address _token)
    external
    view
    override
    returns (uint256)
{
    VaultConfig memory config = tokenToVault[_token];
    if (!config.active) return 0;
    
    // Get base APY from Badger Sett
    uint256 baseAPY = _calculateSettAPY(config.sett);
    
    // Calculate BADGER rewards APY
    uint256 badgerRewardsAPY = _calculateBadgerRewardsAPY(config.sett);
    
    // Add additional APY from staking BADGER if enabled
    uint256 stakingAPY = config.stakeBadger ? _calculateStakingAPY() : 0;
    
    // Combine APYs
    return baseAPY + badgerRewardsAPY + stakingAPY;
}

function _calculateSettAPY(address _sett) internal view returns (uint256) {
    // Calculate Sett APY based on performance fee, harvest frequency, and strategy returns
    // Implementation depends on Badger's specific APY calculation method
    return IBadgerSett(_sett).getPricePerFullShare() * 100 / 1e18;
}

function _calculateBadgerRewardsAPY(address _sett) internal view returns (uint256) {
    // Calculate BADGER rewards APY based on Sett's reward rate and total value locked
    // Implementation depends on Badger's specific reward distribution mechanism
    return IBadgerSett(_sett).getRewardsAPY();
}

function _calculateStakingAPY() internal view returns (uint256) {
    // Calculate staking APY based on bBADGER growth rate
    return bBadgerSett.getPricePerFullShare() * 100 / 1e18;
}

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 Badger adapter with the Yieldinator Facet:

// Register the Badger adapter with the Yieldinator Facet
address badgerAdapter = address(new BadgerAdapter(
    admin,
    badgerToken,
    bBadgerToken,
    bBadgerSett
));
yieldinatorFacet.registerAdapter("Badger DAO", badgerAdapter);

// Register a vault for WBTC
BadgerAdapter(badgerAdapter).registerVault(
    WBTC,
    WBTC_SETT,
    true // Stake BADGER rewards
);

// Add a yield strategy using the Badger adapter
yieldinatorFacet.addYieldStrategy(
    "Badger WBTC Sett",
    badgerAdapter,
    WBTC,
    2, // Medium risk level
    1500 // 15.00% APY
);

Security Considerations

  • Strategy Risk: Badger Sett vaults employ complex yield farming strategies that may expose users to additional risks from the underlying protocols.

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

  • Impermanent Loss: Some Badger strategies involve providing liquidity to AMMs, which can lead to impermanent loss.

  • Reward Distribution: Badger's reward distribution mechanisms may change over time, affecting yield calculations.

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.

  • Integration with Badger's risk assessment framework to evaluate strategy risks.

Gas Optimization

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

  • For harvesting rewards, users can choose whether to stake BADGER rewards based on gas costs and expected returns.

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

Future Improvements

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

  • Integration with Badger's Digg rebasing token strategies

  • Support for Badger's cross-chain vaults as they become available

  • Automated reinvestment of harvested rewards for maximum yield

  • Integration with Badger's ibBTC (interest-bearing BTC) product