StakingStrategy

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/staking/StakingStrategy.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { AbstractStakingStrategy } from "./AbstractStakingStrategy.sol";
import { ADDRESS_REGISTRY, CHAIN_ID_MAINNET } from "../utils/Constants.sol";

contract StakingStrategy is AbstractStakingStrategy {
    constructor(
        address _asset,
        address _yieldToken,
        uint256 _feeRate
    )
        AbstractStakingStrategy(_asset, _yieldToken, _feeRate, ADDRESS_REGISTRY.getWithdrawRequestManager(_yieldToken))
    {
        require(block.chainid == CHAIN_ID_MAINNET);
    }

    function strategy() public pure override returns (string memory) {
        return "Staking";
    }
}
"
    },
    "src/staking/AbstractStakingStrategy.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { AbstractYieldStrategy } from "../AbstractYieldStrategy.sol";
import { IWithdrawRequestManager, WithdrawRequest } from "../interfaces/IWithdrawRequestManager.sol";
import { Trade, TradeType } from "../interfaces/ITradingModule.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";

struct RedeemParams {
    uint8 dexId;
    uint256 minPurchaseAmount;
    bytes exchangeData;
}

struct DepositParams {
    uint8 dexId;
    uint256 minPurchaseAmount;
    bytes exchangeData;
}

/**
 * Supports vaults that borrow a token and stake it into a token that earns yield but may
 * require some illiquid redemption period.
 */
abstract contract AbstractStakingStrategy is AbstractYieldStrategy {
    using TokenUtils for ERC20;

    address internal immutable withdrawToken;

    constructor(
        address _asset,
        address _yieldToken,
        uint256 _feeRate,
        IWithdrawRequestManager _withdrawRequestManager
    )
        AbstractYieldStrategy(_asset, _yieldToken, _feeRate, ERC20(_yieldToken).decimals())
    {
        // For Pendle PT the yield token does not define the withdraw request manager,
        // it is the token out sy
        withdrawRequestManager = _withdrawRequestManager;

        if (address(withdrawRequestManager) != address(0)) {
            accountingAsset = withdrawRequestManager.STAKING_TOKEN();
            withdrawToken = withdrawRequestManager.WITHDRAW_TOKEN();
        } else {
            withdrawToken = address(0);
            // Accounting asset will be set to the asset itself if no withdraw
            // request manager is set. For Pendle PT strategies this will be
            // set to the token in sy.
        }
    }

    /// @notice Returns the total value in terms of the borrowed token of the account's position
    function convertToAssets(uint256 shares) public view override returns (uint256) {
        if (t_CurrentAccount != address(0) && _isWithdrawRequestPending(t_CurrentAccount)) {
            (bool hasRequest, uint256 value) =
                withdrawRequestManager.getWithdrawRequestValue(address(this), t_CurrentAccount, asset, shares);

            // If the account does not have a withdraw request then this will fall through
            // to the super implementation.
            if (hasRequest) return value;
        }

        return super.convertToAssets(shares);
    }

    function _initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesHeld,
        bytes memory data,
        address forceWithdrawFrom
    )
        internal
        virtual
        override
        returns (uint256 requestId)
    {
        ERC20(yieldToken).checkApprove(address(withdrawRequestManager), yieldTokenAmount);
        requestId = withdrawRequestManager.initiateWithdraw({
            account: account,
            yieldTokenAmount: yieldTokenAmount,
            sharesAmount: sharesHeld,
            data: data,
            forceWithdrawFrom: forceWithdrawFrom
        });
    }

    /// @dev By default we can use the withdraw request manager to stake the tokens
    function _mintYieldTokens(
        uint256 assets,
        address, /* receiver */
        bytes memory depositData
    )
        internal
        virtual
        override
    {
        ERC20(asset).checkApprove(address(withdrawRequestManager), assets);
        withdrawRequestManager.stakeTokens(address(asset), assets, depositData);
    }

    function _redeemShares(
        uint256 sharesToRedeem,
        address sharesOwner,
        bool isEscrowed,
        bytes memory redeemData
    )
        internal
        override
    {
        if (isEscrowed) {
            (WithdrawRequest memory w, /* */ ) = withdrawRequestManager.getWithdrawRequest(address(this), sharesOwner);
            uint256 yieldTokensBurned = uint256(w.yieldTokenAmount) * sharesToRedeem / w.sharesAmount;

            uint256 tokensClaimed = withdrawRequestManager.finalizeAndRedeemWithdrawRequest({
                account: sharesOwner,
                withdrawYieldTokenAmount: yieldTokensBurned,
                sharesToBurn: sharesToRedeem
            });

            // Trades may be required here if the borrowed token is not the same as what is
            // received when redeeming.
            if (asset != withdrawToken) {
                RedeemParams memory params = abi.decode(redeemData, (RedeemParams));
                Trade memory trade = Trade({
                    tradeType: TradeType.EXACT_IN_SINGLE,
                    sellToken: address(withdrawToken),
                    buyToken: address(asset),
                    amount: tokensClaimed,
                    limit: params.minPurchaseAmount,
                    deadline: block.timestamp,
                    exchangeData: params.exchangeData
                });

                _executeTrade(trade, params.dexId);
            }
        } else {
            uint256 yieldTokensBurned = convertSharesToYieldToken(sharesToRedeem);
            _executeInstantRedemption(yieldTokensBurned, redeemData);
        }
    }

    /// @notice Default implementation for an instant redemption is to sell the staking token to the
    /// borrow token through the trading module. Can be overridden if required for different implementations.
    function _executeInstantRedemption(
        uint256 yieldTokensToRedeem,
        bytes memory redeemData
    )
        internal
        virtual
        returns (uint256 assetsPurchased)
    {
        RedeemParams memory params = abi.decode(redeemData, (RedeemParams));
        Trade memory trade = Trade({
            tradeType: TradeType.EXACT_IN_SINGLE,
            sellToken: address(yieldToken),
            buyToken: address(asset),
            amount: yieldTokensToRedeem,
            limit: params.minPurchaseAmount,
            deadline: block.timestamp,
            exchangeData: params.exchangeData
        });

        // Executes a trade on the given Dex, the vault must have permissions set for
        // each dex and token it wants to sell.
        ( /* */ , assetsPurchased) = _executeTrade(trade, params.dexId);
    }

    /* solhint-disable no-empty-blocks */
    function _preLiquidation(address, address, uint256, uint256) internal override { /* no-op */ }
    /* solhint-enable no-empty-blocks */

    function _postLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidator
    )
        internal
        override
        returns (bool didTokenize)
    {
        if (address(withdrawRequestManager) != address(0)) {
            // No need to accrue fees because neither the total supply or total yield token balance is changing. If
            // there
            // is no withdraw request then this will be a noop.
            didTokenize =
                withdrawRequestManager.tokenizeWithdrawRequest(liquidateAccount, liquidator, sharesToLiquidator);
        }
    }
}
"
    },
    "src/utils/Constants.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { WETH9 } from "../interfaces/IWETH.sol";
import { AddressRegistry } from "../proxy/AddressRegistry.sol";

address constant ETH_ADDRESS = address(0);
address constant ALT_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 constant DEFAULT_PRECISION = 1e18;
uint256 constant DEFAULT_DECIMALS = 18;
uint256 constant SHARE_PRECISION = 1e24;
uint256 constant VIRTUAL_SHARES = 1e6;

uint256 constant COOLDOWN_PERIOD = 5 minutes;
uint256 constant YEAR = 365 days;

// Will move these to a deployment file when we go to multiple chains
uint256 constant CHAIN_ID_MAINNET = 1;
WETH9 constant WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
AddressRegistry constant ADDRESS_REGISTRY = AddressRegistry(0xe335d314BD4eF7DD44F103dC124FEFb7Ce63eC95);
"
    },
    "src/AbstractYieldStrategy.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import { DEFAULT_DECIMALS, DEFAULT_PRECISION, YEAR, ADDRESS_REGISTRY, VIRTUAL_SHARES } from "./utils/Constants.sol";

import {
    Unauthorized,
    UnauthorizedLendingMarketTransfer,
    InsufficientSharesHeld,
    CannotEnterPosition,
    CurrentAccountAlreadySet
} from "./interfaces/Errors.sol";
import { IYieldStrategy } from "./interfaces/IYieldStrategy.sol";
import { IOracle } from "./interfaces/Morpho/IOracle.sol";
import { TokenUtils } from "./utils/TokenUtils.sol";
import { Trade, TradeType, TRADING_MODULE, nProxy } from "./interfaces/ITradingModule.sol";
import { IWithdrawRequestManager } from "./interfaces/IWithdrawRequestManager.sol";
import { Initializable } from "./proxy/Initializable.sol";
import { MORPHO } from "./interfaces/Morpho/IMorpho.sol";

/// @title AbstractYieldStrategy
/// @notice This is the base contract for all yield strategies, it implements the core logic for
/// minting, burning and the valuation of tokens.
abstract contract AbstractYieldStrategy is Initializable, ERC20, ReentrancyGuardTransient, IYieldStrategy {
    using TokenUtils for ERC20;
    using SafeERC20 for ERC20;

    uint256 internal constant VIRTUAL_YIELD_TOKENS = 1;
    uint256 internal constant SHARE_PRECISION = DEFAULT_PRECISION * VIRTUAL_SHARES;

    /// @inheritdoc IYieldStrategy
    address public immutable override asset;
    /// @inheritdoc IYieldStrategy
    address public immutable override yieldToken;
    /// @inheritdoc IYieldStrategy
    uint256 public immutable override feeRate;
    /// @inheritdoc IYieldStrategy
    address public immutable override accountingAsset;

    IWithdrawRequestManager internal immutable withdrawRequestManager;

    uint8 internal immutable _yieldTokenDecimals;
    uint8 internal immutable _assetDecimals;
    uint256 internal immutable _feeAdjustmentPrecision;

    /**
     * Storage Variables ********
     */
    string private s_name;
    string private s_symbol;

    uint32 private s_lastFeeAccrualTime;
    uint256 private s_accruedFeesInYieldToken;
    uint256 private s_escrowedShares;
    uint256 internal s_yieldTokenBalance;
    /**
     * End Storage Variables *****
     */

    /**
     * Transient Variables ********
     */
    // Used to adjust the valuation call of price(), is set on some methods and
    // cleared by the lending router using clearCurrentAccount(). This is required to
    // ensure that the variable is set throughout the entire context of the lending router
    // call.
    address internal transient t_CurrentAccount;
    // Set and cleared on every call to a lending router authorized method
    address internal transient t_CurrentLendingRouter;
    // Used to authorize transfers off of the lending market
    address internal transient t_AllowTransfer_To;
    uint256 internal transient t_AllowTransfer_Amount;
    /**
     * End Transient Variables *****
     */

    constructor(address _asset, address _yieldToken, uint256 _feeRate, uint8 __yieldTokenDecimals) ERC20("", "") {
        feeRate = _feeRate;
        asset = address(_asset);
        yieldToken = address(_yieldToken);
        // Not all yield tokens have a decimals() function (i.e. Convex staked tokens), so we
        // do have to pass in the decimals as a parameter.
        _yieldTokenDecimals = __yieldTokenDecimals;
        _assetDecimals = TokenUtils.getDecimals(_asset);
        accountingAsset = address(_asset);

        if (_yieldTokenDecimals < 18) {
            _feeAdjustmentPrecision = 10 ** (18 - _yieldTokenDecimals);
        } else {
            _feeAdjustmentPrecision = 1;
        }
    }

    function name() public view override(ERC20, IERC20Metadata) returns (string memory) {
        return s_name;
    }

    function symbol() public view override(ERC20, IERC20Metadata) returns (string memory) {
        return s_symbol;
    }

    function decimals() public view override(ERC20, IERC20Metadata) returns (uint8) {
        return _yieldTokenDecimals + 6;
    }

    /**
     * Valuation and Conversion Functions **
     */

    /// @inheritdoc IYieldStrategy
    function convertSharesToYieldToken(uint256 shares) public view override returns (uint256) {
        // NOTE: rounds down on division
        return (shares * (s_yieldTokenBalance - feesAccrued() + VIRTUAL_YIELD_TOKENS)) / (effectiveSupply());
    }

    /// @inheritdoc IYieldStrategy
    function convertYieldTokenToShares(uint256 yieldTokens) public view returns (uint256) {
        // NOTE: rounds down on division
        return (yieldTokens * effectiveSupply()) / (s_yieldTokenBalance - feesAccrued() + VIRTUAL_YIELD_TOKENS);
    }

    /// @inheritdoc IYieldStrategy
    function convertToShares(uint256 assets) public view override returns (uint256) {
        // NOTE: rounds down on division
        uint256 yieldTokens = assets * (10 ** (_yieldTokenDecimals + DEFAULT_DECIMALS))
            / (convertYieldTokenToAsset() * (10 ** _assetDecimals));
        return convertYieldTokenToShares(yieldTokens);
    }

    /// @inheritdoc IOracle
    function price() public view override returns (uint256) {
        require(_reentrancyGuardEntered() == false);
        // Disable direct borrowing from Morpho, but allow any other callers to see
        // the proper price.
        if (msg.sender == address(MORPHO) && t_CurrentAccount == address(0)) return 0;
        return convertToAssets(SHARE_PRECISION) * (10 ** (36 - 24));
    }

    /// @inheritdoc IYieldStrategy
    function price(address borrower) external override nonReentrant returns (uint256) {
        // Do not change the current account in this method since this method is not
        // authenticated and we do not want to have any unexpected side effects.
        address prevCurrentAccount = t_CurrentAccount;

        t_CurrentAccount = borrower;
        uint256 p = convertToAssets(SHARE_PRECISION) * (10 ** (36 - 24));

        t_CurrentAccount = prevCurrentAccount;
        return p;
    }

    /// @inheritdoc IYieldStrategy
    function totalAssets() public view override returns (uint256) {
        return convertToAssets(totalSupply());
    }

    /// @inheritdoc IYieldStrategy
    function convertYieldTokenToAsset() public view returns (uint256) {
        // The trading module always returns a positive rate in 18 decimals so we can safely
        // cast to uint256
        (int256 rate, /* */ ) = TRADING_MODULE.getOraclePrice(yieldToken, asset);
        return uint256(rate);
    }

    /// @inheritdoc IYieldStrategy
    function effectiveSupply() public view returns (uint256) {
        return (totalSupply() - s_escrowedShares + VIRTUAL_SHARES);
    }

    function getInternalStorage()
        external
        view
        returns (
            uint256 yieldTokenBalance,
            uint256 escrowedShares,
            uint256 accruedFeesInYieldToken,
            uint256 lastFeeAccrualTime
        )
    {
        return (s_yieldTokenBalance, s_escrowedShares, s_accruedFeesInYieldToken, s_lastFeeAccrualTime);
    }

    /**
     * Fee Methods **
     */

    /// @inheritdoc IYieldStrategy
    function feesAccrued() public view override returns (uint256 feesAccruedInYieldToken) {
        return (s_accruedFeesInYieldToken + _calculateAdditionalFeesInYieldToken()) / _feeAdjustmentPrecision;
    }

    /// @inheritdoc IYieldStrategy
    function collectFees() external override nonReentrant returns (uint256 feesCollected) {
        _accrueFees();
        feesCollected = s_accruedFeesInYieldToken / _feeAdjustmentPrecision;
        s_yieldTokenBalance -= feesCollected;
        s_accruedFeesInYieldToken -= (feesCollected * _feeAdjustmentPrecision);
        _transferYieldTokenToOwner(ADDRESS_REGISTRY.feeReceiver(), feesCollected);

        emit FeesCollected(feesCollected);
    }

    /**
     * Core Functions **
     */
    modifier onlyLendingRouter() {
        if (ADDRESS_REGISTRY.isLendingRouter(msg.sender) == false) revert Unauthorized(msg.sender);
        t_CurrentLendingRouter = msg.sender;
        _;
        delete t_CurrentLendingRouter;
    }

    modifier setCurrentAccount(address onBehalf) {
        if (t_CurrentAccount == address(0)) {
            t_CurrentAccount = onBehalf;
        } else if (t_CurrentAccount != onBehalf) {
            revert CurrentAccountAlreadySet();
        }

        _;
    }

    /// @inheritdoc IYieldStrategy
    function clearCurrentAccount() external override onlyLendingRouter {
        delete t_CurrentAccount;
    }

    function mintShares(
        uint256 assetAmount,
        address receiver,
        bytes calldata depositData
    )
        external
        override
        onlyLendingRouter
        setCurrentAccount(receiver)
        nonReentrant
        returns (uint256 sharesMinted)
    {
        // Cannot mint shares if the receiver has an active withdraw request
        if (_isWithdrawRequestPending(receiver)) revert CannotEnterPosition();
        ERC20(asset).safeTransferFrom(t_CurrentLendingRouter, address(this), assetAmount);
        sharesMinted = _mintSharesGivenAssets(assetAmount, depositData, receiver);

        t_AllowTransfer_To = t_CurrentLendingRouter;
        t_AllowTransfer_Amount = sharesMinted;
        // Transfer the shares to the lending router so it can supply collateral
        _transfer(receiver, t_CurrentLendingRouter, sharesMinted);
        _checkInvariant();
    }

    function burnShares(
        address sharesOwner,
        uint256 sharesToBurn,
        uint256 sharesHeld,
        bytes calldata redeemData
    )
        external
        override
        onlyLendingRouter
        setCurrentAccount(sharesOwner)
        nonReentrant
        returns (uint256 assetsWithdrawn)
    {
        assetsWithdrawn = _burnShares(sharesToBurn, sharesHeld, redeemData, sharesOwner);

        // Send all the assets back to the lending router
        ERC20(asset).safeTransfer(t_CurrentLendingRouter, assetsWithdrawn);
        _checkInvariant();
    }

    function allowTransfer(
        address to,
        uint256 amount,
        address currentAccount
    )
        external
        setCurrentAccount(currentAccount)
        onlyLendingRouter
    {
        // Sets the transient variables to allow the lending market to transfer shares on exit position
        // or liquidation.
        t_AllowTransfer_To = to;
        t_AllowTransfer_Amount = amount;
    }

    function preLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidate,
        uint256 accountSharesHeld
    )
        external
        onlyLendingRouter
        nonReentrant
    {
        t_CurrentAccount = liquidateAccount;
        // Liquidator cannot liquidate if they have an active withdraw request, including a tokenized
        // withdraw request.
        if (_isWithdrawRequestPending(liquidator)) revert CannotEnterPosition();
        // Cannot receive a pending withdraw request if the liquidator has a balanceOf
        if (_isWithdrawRequestPending(liquidateAccount) && balanceOf(liquidator) > 0) {
            revert CannotEnterPosition();
        }
        _preLiquidation(liquidateAccount, liquidator, sharesToLiquidate, accountSharesHeld);

        // Allow transfers to the lending router which will proxy the call to liquidate.
        t_AllowTransfer_To = msg.sender;
        t_AllowTransfer_Amount = sharesToLiquidate;
    }

    function postLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidator
    )
        external
        onlyLendingRouter
        nonReentrant
    {
        t_AllowTransfer_To = liquidator;
        t_AllowTransfer_Amount = sharesToLiquidator;
        // Transfer the shares to the liquidator from the lending router
        _transfer(t_CurrentLendingRouter, liquidator, sharesToLiquidator);

        _postLiquidation(liquidator, liquidateAccount, sharesToLiquidator);

        // Clear the transient variables to prevent re-use in a future call.
        delete t_CurrentAccount;

        ADDRESS_REGISTRY.emitAccountNativePosition(liquidator, false);
        _checkInvariant();
    }

    /// @inheritdoc IYieldStrategy
    /// @dev We do not set the current account here because valuation is not done in this method.
    /// A native balance does not require a collateral check.
    function redeemNative(
        uint256 sharesToRedeem,
        bytes memory redeemData
    )
        external
        override
        nonReentrant
        returns (uint256 assetsWithdrawn)
    {
        uint256 sharesHeld = balanceOf(msg.sender);
        if (sharesHeld == 0) revert InsufficientSharesHeld();

        assetsWithdrawn = _burnShares(sharesToRedeem, sharesHeld, redeemData, msg.sender);
        ERC20(asset).safeTransfer(msg.sender, assetsWithdrawn);

        if (sharesHeld == sharesToRedeem) ADDRESS_REGISTRY.emitAccountNativePosition(msg.sender, true);
        _checkInvariant();
    }

    /// @inheritdoc IYieldStrategy
    function initiateWithdraw(
        address account,
        uint256 sharesHeld,
        bytes calldata data,
        address forceWithdrawFrom
    )
        external
        override
        nonReentrant
        onlyLendingRouter
        setCurrentAccount(account)
        returns (uint256 requestId)
    {
        requestId = _withdraw(account, sharesHeld, data, forceWithdrawFrom);
        _checkInvariant();
    }

    /// @inheritdoc IYieldStrategy
    /// @dev We do not set the current account here because valuation is not done in this method. A
    /// native balance does not require a collateral check.
    function initiateWithdrawNative(bytes memory data) external override nonReentrant returns (uint256 requestId) {
        requestId = _withdraw(msg.sender, balanceOf(msg.sender), data, address(0));
        _checkInvariant();
    }

    function _withdraw(
        address account,
        uint256 sharesHeld,
        bytes memory data,
        address forceWithdrawFrom
    )
        internal
        returns (uint256 requestId)
    {
        if (sharesHeld == 0) revert InsufficientSharesHeld();

        // Accrue fees before initiating a withdraw since it will change the effective supply
        _accrueFees();
        uint256 yieldTokenAmount = convertSharesToYieldToken(sharesHeld);
        requestId = _initiateWithdraw(account, yieldTokenAmount, sharesHeld, data, forceWithdrawFrom);
        // Revert in the edge case that the withdraw request is not created.
        require(requestId > 0);
        // Escrow the shares after the withdraw since it will change the effective supply
        // during reward claims when using the RewardManagerMixin.
        s_escrowedShares += sharesHeld;
        s_yieldTokenBalance -= yieldTokenAmount;
    }

    /**
     * Private Functions **
     */
    function _calculateAdditionalFeesInYieldToken() private view returns (uint256 additionalFeesInYieldToken) {
        uint256 timeSinceLastFeeAccrual = block.timestamp - s_lastFeeAccrualTime;
        // e ^ (feeRate * timeSinceLastFeeAccrual / YEAR)
        uint256 x = (feeRate * timeSinceLastFeeAccrual) / YEAR;
        if (x == 0) return 0;
        // Don't accrue fees on a dust balance of yield tokens.
        if (s_yieldTokenBalance < VIRTUAL_SHARES) return 0;

        uint256 preFeeUserHeldYieldTokens = s_yieldTokenBalance * _feeAdjustmentPrecision - s_accruedFeesInYieldToken;
        // Taylor approximation of e ^ x = 1 + x + x^2 / 2! + x^3 / 3! + ...
        uint256 eToTheX = DEFAULT_PRECISION + x + (x * x) / (2 * DEFAULT_PRECISION)
            + (x * x * x) / (6 * DEFAULT_PRECISION * DEFAULT_PRECISION);
        // Decay the user's yield tokens by e ^ (feeRate * timeSinceLastFeeAccrual / YEAR)
        uint256 postFeeUserHeldYieldTokens = preFeeUserHeldYieldTokens * DEFAULT_PRECISION / eToTheX;

        additionalFeesInYieldToken = preFeeUserHeldYieldTokens - postFeeUserHeldYieldTokens;
    }

    function _accrueFees() private {
        if (s_lastFeeAccrualTime == block.timestamp) return;
        // NOTE: this has to be called before any mints or burns.
        s_accruedFeesInYieldToken += _calculateAdditionalFeesInYieldToken();
        s_lastFeeAccrualTime = uint32(block.timestamp);
    }

    function _update(address from, address to, uint256 value) internal override {
        if (from != address(0) && to != address(0)) {
            // Any transfers off of the lending market must be authorized here, this means that native balances
            // held cannot be transferred.
            if (t_AllowTransfer_To != to) revert UnauthorizedLendingMarketTransfer(from, to, value);
            if (t_AllowTransfer_Amount < value) revert UnauthorizedLendingMarketTransfer(from, to, value);

            delete t_AllowTransfer_To;
            delete t_AllowTransfer_Amount;
        }

        super._update(from, to, value);
    }

    /**
     * Internal Helper Functions **
     */
    function _checkInvariant() internal view {
        require(s_yieldTokenBalance <= ERC20(yieldToken).balanceOf(address(this)));
    }

    function _isWithdrawRequestPending(address account) internal view virtual returns (bool) {
        return address(withdrawRequestManager) != address(0)
            && withdrawRequestManager.isPendingWithdrawRequest(address(this), account);
    }

    /// @dev Can be used to delegate call to the TradingModule's implementation in order to execute a trade
    function _executeTrade(
        Trade memory trade,
        uint16 dexId
    )
        internal
        returns (uint256 amountSold, uint256 amountBought)
    {
        if (trade.tradeType == TradeType.STAKE_TOKEN) {
            IWithdrawRequestManager wrm = ADDRESS_REGISTRY.getWithdrawRequestManager(trade.buyToken);
            ERC20(trade.sellToken).checkApprove(address(wrm), trade.amount);
            amountBought = wrm.stakeTokens(trade.sellToken, trade.amount, trade.exchangeData);
            emit TradeExecuted(trade.sellToken, trade.buyToken, trade.amount, amountBought);

            return (trade.amount, amountBought);
        } else {
            address implementation = nProxy(payable(address(TRADING_MODULE))).getImplementation();
            bytes memory result = _delegateCall(
                implementation, abi.encodeWithSelector(TRADING_MODULE.executeTrade.selector, dexId, trade)
            );
            (amountSold, amountBought) = abi.decode(result, (uint256, uint256));
        }
    }

    function _delegateCall(address target, bytes memory data) internal returns (bytes memory result) {
        bool success;
        (success, result) = target.delegatecall(data);
        if (!success) {
            assembly {
                // Copy the return data to memory
                returndatacopy(0, 0, returndatasize())
                // Revert with the return data
                revert(0, returndatasize())
            }
        }
    }

    /**
     * Virtual Functions **
     */
    function _initialize(bytes calldata data) internal virtual override {
        (string memory _name, string memory _symbol) = abi.decode(data, (string, string));
        s_name = _name;
        s_symbol = _symbol;

        s_lastFeeAccrualTime = uint32(block.timestamp);
        emit VaultCreated(address(this));
    }

    /// @dev Marked as virtual to allow for RewardManagerMixin to override
    function _mintSharesGivenAssets(
        uint256 assets,
        bytes memory depositData,
        address receiver
    )
        internal
        virtual
        returns (uint256 sharesMinted)
    {
        if (assets == 0) return 0;

        // First accrue fees on the yield token
        _accrueFees();
        uint256 yieldTokensBefore = ERC20(yieldToken).balanceOf(address(this));
        _mintYieldTokens(assets, receiver, depositData);
        uint256 yieldTokensAfter = ERC20(yieldToken).balanceOf(address(this));
        uint256 yieldTokensMinted = yieldTokensAfter - yieldTokensBefore;

        sharesMinted =
            (yieldTokensMinted * effectiveSupply()) / (s_yieldTokenBalance - feesAccrued() + VIRTUAL_YIELD_TOKENS);

        s_yieldTokenBalance += yieldTokensMinted;
        _mint(receiver, sharesMinted);
    }

    /// @dev Marked as virtual to allow for RewardManagerMixin to override
    function _burnShares(
        uint256 sharesToBurn,
        uint256, /* sharesHeld */
        bytes memory redeemData,
        address sharesOwner
    )
        internal
        virtual
        returns (uint256 assetsWithdrawn)
    {
        if (sharesToBurn == 0) return 0;
        bool isEscrowed = _isWithdrawRequestPending(sharesOwner);

        uint256 initialAssetBalance = TokenUtils.tokenBalance(asset);

        // First accrue fees on the yield token
        _accrueFees();
        uint256 yieldTokensBefore = ERC20(yieldToken).balanceOf(address(this));
        _redeemShares(sharesToBurn, sharesOwner, isEscrowed, redeemData);
        if (isEscrowed) s_escrowedShares -= sharesToBurn;
        uint256 yieldTokensAfter = ERC20(yieldToken).balanceOf(address(this));
        uint256 yieldTokensRedeemed = yieldTokensBefore - yieldTokensAfter;
        s_yieldTokenBalance -= yieldTokensRedeemed;

        uint256 finalAssetBalance = TokenUtils.tokenBalance(asset);
        assetsWithdrawn = finalAssetBalance - initialAssetBalance;

        // This burns the shares from the sharesOwner's balance
        _burn(sharesOwner, sharesToBurn);
    }

    /// @dev Some yield tokens (such as Convex staked tokens) cannot be transferred, so we may need
    /// to override this function.
    function _transferYieldTokenToOwner(address owner, uint256 yieldTokens) internal virtual {
        ERC20(yieldToken).safeTransfer(owner, yieldTokens);
    }

    /// @dev Returns the maximum number of shares that can be liquidated. Allows the strategy to override the
    /// underlying lending market's liquidation logic.
    function _preLiquidation(
        address liquidateAccount,
        address liquidator,
        uint256 sharesToLiquidate,
        uint256 accountSharesHeld
    )
        internal
        virtual;

    /// @dev Called after liquidation
    function _postLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidator
    )
        internal
        virtual
        returns (bool didTokenize);

    /// @dev Mints yield tokens given a number of assets.
    function _mintYieldTokens(uint256 assets, address receiver, bytes memory depositData) internal virtual;

    /// @dev Redeems shares
    function _redeemShares(
        uint256 sharesToRedeem,
        address sharesOwner,
        bool isEscrowed,
        bytes memory redeemData
    )
        internal
        virtual;

    function _initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesHeld,
        bytes memory data,
        address forceWithdrawFrom
    )
        internal
        virtual
        returns (uint256 requestId);

    /// @inheritdoc IYieldStrategy
    function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
        uint256 yieldTokens = convertSharesToYieldToken(shares);
        // NOTE: rounds down on division
        return (yieldTokens * convertYieldTokenToAsset() * (10 ** _assetDecimals))
            / (10 ** (_yieldTokenDecimals + DEFAULT_DECIMALS));
    }
}
"
    },
    "src/interfaces/IWithdrawRequestManager.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { TradeType } from "./ITradingModule.sol";

/// Each withdraw request manager contract is responsible for managing withdraws of a token
/// from a specific token (i.e. wstETH, weETH, sUSDe, etc). Each yield strategy can call the
/// appropriate withdraw request manager to initiate a withdraw of a given yield token.

struct StakingTradeParams {
    TradeType tradeType;
    uint256 minPurchaseAmount;
    bytes exchangeData;
    uint16 dexId;
    bytes stakeData;
}

struct WithdrawRequest {
    uint256 requestId;
    uint120 yieldTokenAmount;
    uint120 sharesAmount;
}

struct TokenizedWithdrawRequest {
    uint120 totalYieldTokenAmount;
    uint120 totalWithdraw;
    bool finalized;
}

interface IWithdrawRequestManager {
    event ApprovedVault(address indexed vault, bool indexed isApproved);
    event InitiateWithdrawRequest(
        address indexed account,
        address indexed vault,
        uint256 yieldTokenAmount,
        uint256 sharesAmount,
        uint256 requestId
    );

    event WithdrawRequestTokenized(
        address indexed from, address indexed to, address indexed vault, uint256 requestId, uint256 sharesAmount
    );

    event WithdrawRequestFinalized(
        address indexed vault, address indexed account, uint256 requestId, uint256 totalWithdraw
    );

    event WithdrawRequestRedeemed(
        address indexed vault,
        address indexed account,
        uint256 requestId,
        uint256 withdrawYieldTokenAmount,
        uint256 sharesBurned,
        bool isCleared
    );

    /// @notice Returns the token that will be the result of staking
    /// @return yieldToken the yield token of the withdraw request manager
    function YIELD_TOKEN() external view returns (address);

    /// @notice Returns the token that will be the result of the withdraw request
    /// @return withdrawToken the withdraw token of the withdraw request manager
    function WITHDRAW_TOKEN() external view returns (address);

    /// @notice Returns the token that will be used to stake
    /// @return stakingToken the staking token of the withdraw request manager
    function STAKING_TOKEN() external view returns (address);

    /// @notice Returns whether a vault is approved to initiate withdraw requests
    /// @param vault the vault to check the approval for
    /// @return isApproved whether the vault is approved
    function isApprovedVault(address vault) external view returns (bool);

    /// @notice Returns whether a vault has a pending withdraw request
    /// @param vault the vault to check the pending withdraw request for
    /// @param account the account to check the pending withdraw request for
    /// @return isPending whether the vault has a pending withdraw request
    function isPendingWithdrawRequest(address vault, address account) external view returns (bool);

    /// @notice Sets whether a vault is approved to initiate withdraw requests
    /// @param vault the vault to set the approval for
    /// @param isApproved whether the vault is approved
    function setApprovedVault(address vault, bool isApproved) external;

    /// @notice Stakes the deposit token to the yield token and transfers it back to the vault
    /// @dev Only approved vaults can stake tokens
    /// @param depositToken the token to stake, will be transferred from the vault
    /// @param amount the amount of tokens to stake
    /// @param data additional data for the stake
    function stakeTokens(
        address depositToken,
        uint256 amount,
        bytes calldata data
    )
        external
        returns (uint256 yieldTokensMinted);

    /// @notice Initiates a withdraw request
    /// @dev Only approved vaults can initiate withdraw requests
    /// @param account the account to initiate the withdraw request for
    /// @param yieldTokenAmount the amount of yield tokens to withdraw
    /// @param sharesAmount the amount of shares to withdraw, used to mark the shares to
    /// yield token ratio at the time of the withdraw request
    /// @param data additional data for the withdraw request
    /// @return requestId the request id of the withdraw request
    function initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesAmount,
        bytes calldata data,
        address forceWithdrawFrom
    )
        external
        returns (uint256 requestId);

    /// @notice Attempts to redeem active withdraw requests during vault exit
    /// @dev Will revert if the withdraw request is not finalized
    /// @param account the account to finalize and redeem the withdraw request for
    /// @param withdrawYieldTokenAmount the amount of yield tokens to withdraw
    /// @param sharesToBurn the amount of shares to burn for the yield token
    /// @return tokensWithdrawn amount of withdraw tokens redeemed from the withdraw requests
    function finalizeAndRedeemWithdrawRequest(
        address account,
        uint256 withdrawYieldTokenAmount,
        uint256 sharesToBurn
    )
        external
        returns (uint256 tokensWithdrawn);

    /// @notice Finalizes withdraw requests outside of a vault exit. This may be required in cases if an
    /// account is negligent in exiting their vault position and letting the withdraw request sit idle
    /// could result in losses. The withdraw request is finalized and stored in a tokenized withdraw request
    /// where the account has the full claim on the withdraw.
    /// @dev No access control is enforced on this function but no tokens are transferred off the request
    /// manager either.
    /// @dev Will revert if the withdraw request is not finalized
    function finalizeRequestManual(address vault, address account) external returns (uint256 tokensWithdrawn);

    /// @notice If an account has an illiquid withdraw request, this method will tokenize their
    /// claim on it during liquidation.
    /// @dev Only approved vaults can tokenize withdraw requests
    /// @param from the account that is being liquidated
    /// @param to the liquidator
    /// @param sharesAmount the amount of shares to the liquidator
    function tokenizeWithdrawRequest(
        address from,
        address to,
        uint256 sharesAmount
    )
        external
        returns (bool didTokenize);

    /// @notice Allows the emergency exit role to rescue tokens from the withdraw request manager
    /// @param cooldownHolder the cooldown holder to rescue tokens from
    /// @param token the token to rescue
    /// @param receiver the receiver of the rescued tokens
    /// @param amount the amount of tokens to rescue
    function rescueTokens(address cooldownHolder, address token, address receiver, uint256 amount) external;

    /// @notice Returns whether a withdraw request can be finalized, only used off chain
    /// to determine if a withdraw request can be finalized.
    /// @param requestId the request id of the withdraw request
    /// @return canFinalize whether the withdraw request can be finalized
    function canFinalizeWithdrawRequest(uint256 requestId) external view returns (bool);

    /// @notice Returns the withdraw request and tokenized withdraw request for an account
    /// @param vault the vault to get the withdraw request for
    /// @param account the account to get the withdraw request for
    /// @return w the withdraw request
    /// @return s the tokenized withdraw request
    function getWithdrawRequest(
        address vault,
        address account
    )
        external
        view
        returns (WithdrawRequest memory w, TokenizedWithdrawRequest memory s);

    /// @notice Returns the value of a withdraw request in terms of the asset
    /// @param vault the vault to get the withdraw request for
    /// @param account the account to get the withdraw request for
    /// @param asset the asset to get the value for
    /// @param shares the amount of shares to get the value for
    /// @return hasRequest whether the account has a withdraw request
    /// @return value the value of the withdraw request in terms of the asset
    function getWithdrawRequestValue(
        address vault,
        address account,
        address asset,
        uint256 shares
    )
        external
        view
        returns (bool hasRequest, uint256 value);

    /// @notice Returns the protocol reported exchange rate between the yield token
    /// and then withdraw token.
    /// @return exchangeRate the exchange rate of the yield token to the withdraw token
    function getExchangeRate() external view returns (uint256 exchangeRate);

    /// @notice Returns the known amount of withdraw tokens for a withdraw request
    /// @param requestId the request id of the withdraw request
    /// @return hasKnownAmount whether the amount is known
    /// @return amount the amount of withdraw tokens
    function getKnownWithdrawTokenAmount(uint256 requestId)
        external
        view
        returns (bool hasKnownAmount, uint256 amount);
}
"
    },
    "src/interfaces/ITradingModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.28;

import { AggregatorV2V3Interface } from "./AggregatorV2V3Interface.sol";

enum DexId {
    _UNUSED, // flag = 1,  enum = 0
    UNISWAP_V2, // flag = 2,  enum = 1
    UNISWAP_V3, // flag = 4,  enum = 2
    ZERO_EX, // flag = 8,  enum = 3
    BALANCER_V2, // flag = 16, enum = 4
    // NOTE: this id is unused in the TradingModule
    CURVE, // flag = 32, enum = 5
    NOTIONAL_VAULT, // flag = 64, enum = 6
    CURVE_V2, // flag = 128, enum = 7
    CAMELOT_V3 // flag = 256, enum = 8

}

enum TradeType {
    EXACT_IN_SINGLE, // flag = 1
    EXACT_OUT_SINGLE, // flag = 2
    EXACT_IN_BATCH, // flag = 4
    EXACT_OUT_BATCH, // flag = 8
    STAKE_TOKEN // flag = 16

}

struct UniV3SingleData {
    uint24 fee;
}

// Path is packed encoding `token, fee, token, fee, outToken`
struct UniV3BatchData {
    bytes path;
}

struct CurveV2SingleData {
    // Address of the pool to use for the swap
    address pool;
    int128 fromIndex;
    int128 toIndex;
}

struct CurveV2BatchData {
    // Array of [initial token, pool, token, pool, token, ...]
    // The array is iterated until a pool address of 0x00, then the last
    // given token is transferred to `_receiver`
    address[9] route;
    // Multidimensional array of [i, j, swap type] where i and j are the correct
    // values for the n'th pool in `_route`. The swap type should be
    // 1 for a stableswap `exchange`,
    // 2 for stableswap `exchange_underlying`,
    // 3 for a cryptoswap `exchange`,
    // 4 for a cryptoswap `exchange_underlying`,
    // 5 for factory metapools with lending base pool `exchange_underlying`,
    // 6 for factory crypto-meta pools underlying exchange (`exchange` method in zap),
    // 7-11 for wrapped coin (underlying for lending or fake pool) -> LP token "exchange" (actually `add_liquidity`),
    // 12-14 for LP token -> wrapped coin (underlying for lending pool) "exchange" (actually
    // `remove_liquidity_one_coin`)
    // 15 for WETH -> ETH "exchange" (actually deposit/withdraw)
    uint256[3][4] swapParams;
}

struct Trade {
    TradeType tradeType;
    address sellToken;
    address buyToken;
    uint256 amount;
    /// minBuyAmount or maxSellAmount
    uint256 limit;
    uint256 deadline;
    bytes exchangeData;
}

error InvalidTrade();
error DynamicTradeFailed();
error TradeFailed();

interface nProxy {
    function getImplementation() external view returns (address);
}

interface ITradingModule {
    struct TokenPermissions {
        bool allowSell;
        /// @notice allowed DEXes
        uint32 dexFlags;
        /// @notice allowed trade types
        uint32 tradeTypeFlags;
    }

    event TradeExecuted(address indexed sellToken, address indexed buyToken, uint256 sellAmount, uint256 buyAmount);

    event PriceOracleUpdated(address token, address oracle);
    event MaxOracleFreshnessUpdated(uint32 currentValue, uint32 newValue);
    event TokenPermissionsUpdated(address sender, address token, TokenPermissions permissions);

    function tokenWhitelist(
        address spender,
        address token
    )
        external
        view
        returns (bool allowSell, uint32 dexFlags, uint32 tradeTypeFlags);

    function priceOracles(address token) external view returns (AggregatorV2V3Interface oracle, uint8 rateDecimals);

    function getExecutionData(
        uint16 dexId,
        address from,
        Trade calldata trade
    )
        external
        view
        returns (address spender, address target, uint256 value, bytes memory params);

    function setMaxOracleFreshness(uint32 newMaxOracleFreshnessInSeconds) external;

    function setPriceOracle(address token, AggregatorV2V3Interface oracle) external;

    function setTokenPermissions(address sender, address token, TokenPermissions calldata permissions) external;

    function getOraclePrice(address inToken, address outToken) external view returns (int256 answer, int256 decimals);

    function executeTrade(
        uint16 dexId,
        Trade calldata trade
    )
        external
        payable
        returns (uint256 amountSold, uint256 amountBought);

    function executeTradeWithDynamicSlippage(
        uint16 dexId,
        Trade memory trade,
        uint32 dynamicSlippageLimit
    )
        external
        payable
        returns (uint256 amountSold, uint256 amountBought);

    function getLimitAmount(
        address from,
        TradeType tradeType,
        address sellToken,
        address buyToken,
        uint256 amount,
        uint32 slippageLimit
    )
        external
        view
        returns (uint256 limitAmount);

    function canExecuteTrade(address from, uint16 dexId, Trade calldata trade) external view returns (bool);
}

ITradingModule constant TRADING_MODULE = ITradingModule(0x594734c7e06C3D483466ADBCe401C6Bd269746C8);
"
    },
    "node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "src/utils/TokenUtils.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ETH_ADDRESS, ALT_ETH_ADDRESS } from "./Constants.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

library TokenUtils {
    using SafeERC20 for ERC20;

    function getDecimals(address token) internal view returns (uint8 decimals) {
        decimals = (token == ETH_ADDRESS || token == ALT_ETH_ADDRESS) ? 18 : ERC20(token).decimals();
        require(decimals <= 18);
    }

    function tokenBalance(address token) internal view returns (uint256) {
        return token == ETH_ADDRESS ? address(this).balance : ERC20(token).balanceOf(address(this));
    }

    function checkApprove(ERC20 token, address spender, uint256 amount) internal {
        if (address(token) == address(0)) return;

        token.forceApprove(spender, amount);
    }

    function checkRevoke(ERC20 token, address spender) internal {
        if (address(token) == address(0)) return;
        token.forceApprove(spender, 0);
    }

    function checkReturnCode() internal pure returns (bool success) {
        uint256[1] memory result;
        assembly {
            switch returndatasize()
            case 0 {
                // This is a non-standard ERC-20
                success := 1 // set success to true
            }
            case 32 {
                // This is a compliant ERC-20
                returndatacopy(result, 0, 32)
                success := mload(result) // Set `success = returndata` of external call
            }
            default {
                // This is an excessively non-compliant ERC-20, revert.
                revert(0, 0)
            }
        }
    }
}
"
    },
    "src/interfaces/IWETH.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface WETH9 is IERC20 {
    function deposit() external payable;
    function withdraw(uint256 wad) external;
}
"
    },
    "src/proxy/AddressRegistry.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { Unauthorized, CannotEnterPosition, InvalidVault } from "../interfaces/Errors.sol";
import { IWithdrawRequestManager } from "../interfaces/IWithdrawRequestManager.sol";
import { VaultPosition } from "../interfaces/ILendingRouter.sol";
import { Initializable } from "./Initializable.sol";

/// @notice Registry for the addresses for different components of the protocol.
contract AddressRegistry is Initializable {
    event PendingUpgradeAdminSet(address indexed newPendingUpgradeAdmin);
    event UpgradeAdminTransferred(address indexed newUpgradeAdmin);
    event PendingPauseAdminSet(address indexed newPendingPauseAdmin);
    event PauseAdminTransferred(address indexed newPauseAdmin);
    event FeeReceiverTransferred(address indexed newFeeReceiver);
    event WithdrawRequestManagerSet(address indexed yieldToken, address indexed withdrawRequestManager);
    event LendingRouterSet(address indexed lendingRouter);
    event AccountPositionCreated(address indexed account, address indexed vault, address indexed lendingRouter);
    event AccountPositionCleared(address indexed account, address indexed vault, address indexed lendingRouter);
    event WhitelistedVault(address indexed vault, bool isWhitelisted);

    /// @notice Address of the admin that is allowed to:
    /// - Upgrade TimelockUpgradeableProxy contracts given a 7 day timelock
    /// - Transfer the upgrade admin role
    /// - Set the pause admin
    /// - Set the fee receiver
    /// - Add reward tokens to the RewardManager
    /// - Set the WithdrawRequestManager for a yield token
    /// - Whitelist vaults for the WithdrawRequestManager
    /// - Whitelist new lending routers
    address public upgradeAdmin;
    address public pendingUpgradeAdmin;

    /// @notice Address of the admin that is allowed to selectively pause or unpause
    /// TimelockUpgradeableProxy contracts
    address public pauseAdmin;
    address public pendingPauseAdmin;

    /// @notice Address of the account that receives the protocol fees
    address public feeReceiver;

    /// @notice Mapping of yield token to WithdrawRequestManager
    mapping(address token => address withdrawRequestManager) public withdrawRequestManagers;

    /// @notice Mapping of lending router to boolean indicating if it is whitelisted
    mapping(address lendingRouter => bool isLendingRouter) public lendingRouters;

    /// @notice Mapping to whitelisted vaults
    mapping(address vault => bool isWhitelisted) public whitelistedVaults;

    /// @notice Mapping of accounts to their existing position on a given vault
    mapping(address account => mapping(address vault => VaultPosition)) internal accountPositions;

    function _initialize(bytes calldata data) internal override {
        (address _upgradeAdmin, address _pauseAdmin, address _feeReceiver) =
            abi.decode(data, (address, address, address));
        upgradeAdmin = _upgradeAdmin;
        pauseAdmin = _pauseAdmin;
        feeReceiver = _feeReceiver;
    }

    modifier onlyUpgradeAdmin() {
        if (msg.sender != upgradeAdmin) revert Unauthorized(msg.sender);
        _;
    }

    function transferUpgradeAdmin(address _newUpgradeAdmin) external onlyUpgradeAdmin {
        pendingUpgradeAdmin = _newUpgradeAdmin;
        emit PendingUpgradeAdminSet(_newUpgradeAdmin);
    }

    function acceptUpgradeOwnership() external {
        if (msg.sender != pendingUpgradeAdmin) revert Unauthorized(msg.sender);
        upgradeAdmin = pendingUpgradeAdmin;
        delete pendingUpgradeAdmin;
        emit UpgradeAdminTransferred(upgradeAdmin);
    }

    function transferPauseAdmin(address _newPauseAdmin) external onlyUpgradeAdmin {
        pendingPauseAdmin = _newPauseAdmin;
        emit PendingPauseAdminSet(_newPauseAdmin);
    }

    function acceptPauseAdmin() external {
        if (msg.sender != pendingPauseAdmin) revert Unauthorized(msg.sender);
        pauseAdmin

Tags:
ERC20, ERC165, Multisig, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x2838f999c23b480324314ac8d0c1f84d795135ba|verified:true|block:23478085|tx:0xa249d7596515a3e6fcfc4ee8d688318479a3afa6b95df7a7fddb7ab7e56fd5e9|first_check:1759315173

Submitted on: 2025-10-01 12:39:33

Comments

Log in to comment.

No comments yet.