RewardReceiverMigrationModule

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/modules/RewardReceiverMigrationModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ILiquidityGauge} from "@interfaces/curve/ILiquidityGauge.sol";

import {ProtocolContext} from "src/ProtocolContext.sol";

import {IStrategy} from "src/interfaces/IStrategy.sol";
import {IRewardReceiver} from "src/interfaces/IRewardReceiver.sol";
import {ICurveFactory} from "src/interfaces/IFactoryWithSidecar.sol";
import {ConvexSidecar} from "src/integrations/curve/ConvexSidecar.sol";
import {OnlyBoostAllocator} from "src/integrations/curve/OnlyBoostAllocator.sol";
import {ConvexSidecarFactory} from "src/integrations/curve/ConvexSidecarFactory.sol";

/// @title RewardReceiverMigrationModule.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice RewardReceiverMigrationModule is a module that migrates the reward receiver from a legacy reward receiver to a new reward receiver.
contract RewardReceiverMigrationModule is ProtocolContext {

    /// @notice Cache struct to avoid stack too deep errors.
    struct Cache {
        address vault;
        address strategy;
        address allocator;
        address rewardReceiver;
        address sidecarFactory;
        address sidecar;
        address asset;
        address factory;
        address newRewardReceiverImplementation;
    }

    /// @notice The reward router address.
    address public immutable REWARD_ROUTER;

    /// @notice The old Convex sidecar factory address.
    address public immutable OLD_SIDECAR_FACTORY;

    /// @notice The new Convex sidecar factory address.
    address public immutable NEW_SIDECAR_FACTORY;

    /// @notice Error thrown when the set reward receiver fails.
    error SetRewardReceiverFailed();

    /// @notice Error thrown when the set locker only fails.
    error SetLockerOnlyFailed();

    /// @notice Error thrown when the curve factory does not point to the expected sidecar factory.
    error UnexpectedSidecarFactory(address expected, address actual);

    /// @notice Emitted when the migration is completed.
    event MigrationCompleted(address indexed vault, address indexed gauge, address newSidecar, address newRewardReceiver);

    constructor(bytes4 _protocolId, address _protocolController, address _locker, address _gateway, address _rewardRouter, address _oldSidecarFactory, address _newSidecarFactory)
        ProtocolContext(_protocolId, _protocolController, _locker, _gateway)
    {
        REWARD_ROUTER = _rewardRouter;
        OLD_SIDECAR_FACTORY = _oldSidecarFactory;
        NEW_SIDECAR_FACTORY = _newSidecarFactory;
    }

    /// @notice Migrates the reward receiver for a given gauge.
    /// @dev Because the old ConvexSidecar implementation use reward receiver address as immutable argument, we need to migrate sidecar as well.
    function migrate(address gauge) external {
        /// 0a. Cache the required addresses to avoid stack too deep errors.
        address factory = PROTOCOL_CONTROLLER.factory(PROTOCOL_ID);
        Cache memory cache = Cache({
            vault: PROTOCOL_CONTROLLER.vault(gauge),
            strategy: PROTOCOL_CONTROLLER.strategy(PROTOCOL_ID),
            allocator: PROTOCOL_CONTROLLER.allocator(PROTOCOL_ID),
            rewardReceiver: PROTOCOL_CONTROLLER.rewardReceiver(gauge),
            sidecarFactory: ICurveFactory(factory).CONVEX_SIDECAR_FACTORY(),
            sidecar: ConvexSidecarFactory(OLD_SIDECAR_FACTORY).sidecar(gauge),
            asset: PROTOCOL_CONTROLLER.asset(gauge),
            factory: factory,
            newRewardReceiverImplementation: ICurveFactory(factory).REWARD_RECEIVER_IMPLEMENTATION()
        });

        require (cache.sidecarFactory == NEW_SIDECAR_FACTORY, UnexpectedSidecarFactory(NEW_SIDECAR_FACTORY, cache.sidecarFactory));

        address legacyRewardReceiver = _getLegacyRewardReceiver(cache.rewardReceiver);
        bytes memory data = abi.encodePacked(cache.vault, legacyRewardReceiver, REWARD_ROUTER);

        /// 0b. Check if the migration is possible.
        if(_isAlreadyMigrated(gauge, cache.factory, cache.newRewardReceiverImplementation, data)) return;

        /// 1a. Flush residual rewards if the old sidecar is not zero address.
        uint pid;
        address newSidecar;
        if(cache.sidecar != address(0)) {
            /// 1b. Flush residual rewards using `ConvexSidecar.claimExtraRewards()`
            ConvexSidecar(cache.sidecar).claimExtraRewards();

            /// 1c. Cache the pid.
            pid = ConvexSidecar(cache.sidecar).pid();

            /// 1d. Freeze Convex allocations using `OnlyBoostAllocator.setLockerOnly(gauge, true)`
            require(_executeTransaction(address(cache.allocator), abi.encodeWithSelector(OnlyBoostAllocator.setLockerOnly.selector, gauge, true)), SetLockerOnlyFailed());

            /// 1e. Rebalance the strategy to drain the old sidecar.
            IStrategy(cache.strategy).rebalance(gauge);

            /// 1f. Disable old sidecar in the ProtocolController using `removeValidAllocationTarget(gauge, oldSidecar)` 
            PROTOCOL_CONTROLLER.removeValidAllocationTarget(gauge, cache.sidecar);

            /// 1g. Deploy new sidecar using `ConvexSidecarFactory.create(gauge, abi.encode(pid))` using the new ConvexSidecarFactory contract
            newSidecar = ConvexSidecarFactory(cache.sidecarFactory).create(gauge, abi.encode(pid));

            /// 1i. Reenable Convex allocations using `OnlyBoostAllocator.setLockerOnly(gauge, false)`
            require(_executeTransaction(address(cache.allocator), abi.encodeWithSelector(OnlyBoostAllocator.setLockerOnly.selector, gauge, false)), SetLockerOnlyFailed());

            /// 1j. Repopulate the new sidecar using `Strategy.rebalance(gauge)`
            IStrategy(cache.strategy).rebalance(gauge);
        }


        /// 2 Generate a deterministic salt based on the initialization data.
        bytes32 salt = keccak256(data);

        /// 2b. Clone the reward receiver implementation with the initialization data.
        address newRewardReceiver = Clones.cloneDeterministicWithImmutableArgs(cache.newRewardReceiverImplementation, data, salt);

        /// 3. Update the reward receiver in the ProtocolController.
        PROTOCOL_CONTROLLER.registerVault(gauge, cache.vault, cache.asset, newRewardReceiver, PROTOCOL_ID);

        /// 4. Set the reward receiver for the gauge.
        _setRewardReceiver(gauge, newRewardReceiver);

        /// 5. Emit the migration completed event.
        emit MigrationCompleted(cache.vault, gauge, newSidecar, newRewardReceiver);
    }

    /// @notice Checks if the migration is possible.
    /// @dev The migration is onlu possible if the gauge was not already migrated.
    /// It should not revert. This function is used in the Factory to migrate before syncing extra rewards.
    function _isAlreadyMigrated(address gauge, address factory, address newRewardReceiverImplementation, bytes memory data) internal view returns (bool) {
        return PROTOCOL_CONTROLLER.rewardReceiver(gauge) == getRewardReceiverAddress(newRewardReceiverImplementation, factory, data)
            || PROTOCOL_CONTROLLER.rewardReceiver(gauge) == getRewardReceiverAddress(newRewardReceiverImplementation, address(this), data);
    }

    /// @notice Computes the reward receiver address for a given deployer and data.
    /// @param deployer The deployer of the reward receiver (factory or module).
    /// @return rewardReceiver The address of the reward receiver.
    function getRewardReceiverAddress(address newRewardReceiverImplementation, address deployer, bytes memory data) internal pure returns (address rewardReceiver) {
        bytes32 salt = keccak256(data);
        return Clones.predictDeterministicAddressWithImmutableArgs(newRewardReceiverImplementation, data, salt, deployer);
    }

    /// @notice Computes the legacy reward receiver address for a given current reward receiver.
    /// @param current The current reward receiver address.
    /// @return legacy The legacy reward receiver address.
    function _getLegacyRewardReceiver(address current) internal view returns (address legacy) {
        if (current == address(0)) return current;

        try IRewardReceiver(current).legacyRewardReceiver() returns (address _legacy) {
            legacy = _legacy;
        } catch {
            return current;
        }
    }

    /// @notice Sets the reward receiver for a gauge.
    /// @param _gauge The gauge to set the reward receiver for.
    /// @param _rewardReceiver The reward receiver to set.
    function _setRewardReceiver(address _gauge, address _rewardReceiver) internal {
        /// Set _rewardReceiver as the reward receiver on the gauge.
        bytes memory data = abi.encodeWithSignature("set_rewards_receiver(address)", _rewardReceiver);
        require(_executeTransaction(_gauge, data), SetRewardReceiverFailed());
    }
}
"
    },
    "node_modules/@openzeppelin/contracts/proxy/Clones.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    error CloneArgumentsTooLong();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        return clone(implementation, 0);
    }

    /**
     * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
     * to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function clone(address implementation, uint256 value) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(value, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple times will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        return cloneDeterministic(implementation, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
     * a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministic(
        address implementation,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(value, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create opcode, which should never revert.
     */
    function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
        return cloneWithImmutableArgs(implementation, args, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
     * parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneWithImmutableArgs(
        address implementation,
        bytes memory args,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        assembly ("memory-safe") {
            instance := create(value, add(bytecode, 0x20), mload(bytecode))
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
     * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
     * at the same address.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal returns (address instance) {
        return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
     * but with a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.deploy(value, salt, bytecode);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.computeAddress(salt, keccak256(bytecode), deployer);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
    }

    /**
     * @dev Get the immutable args attached to a clone.
     *
     * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
     *   function will return an empty array.
     * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
     *   `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
     *   creation.
     * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
     *   function should only be used to check addresses that are known to be clones.
     */
    function fetchCloneArgs(address instance) internal view returns (bytes memory) {
        bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
        assembly ("memory-safe") {
            extcodecopy(instance, add(result, 32), 45, mload(result))
        }
        return result;
    }

    /**
     * @dev Helper that prepares the initcode of the proxy with immutable args.
     *
     * An assembly variant of this function requires copying the `args` array, which can be efficiently done using
     * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
     * abi.encodePacked is more expensive but also more portable and easier to review.
     *
     * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
     * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
     */
    function _cloneCodeWithImmutableArgs(
        address implementation,
        bytes memory args
    ) private pure returns (bytes memory) {
        if (args.length > 24531) revert CloneArgumentsTooLong();
        return
            abi.encodePacked(
                hex"61",
                uint16(args.length + 45),
                hex"3d81600a3d39f3363d3d373d3d3d363d73",
                implementation,
                hex"5af43d82803e903d91602b57fd5bf3",
                args
            );
    }
}
"
    },
    "node_modules/@stake-dao/interfaces/src/interfaces/curve/ILiquidityGauge.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

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

interface IL2LiquidityGauge {
    function reward_data(address arg0)
        external
        view
        returns (address distributor, uint256 period_finish, uint256 rate, uint256 last_update, uint256 integral);

    function reward_tokens(uint256 arg0) external view returns (address);
    function is_killed() external view returns (bool);
    function lp_token() external view returns (address);
}

interface ILiquidityGauge is IERC20 {
    event ApplyOwnership(address admin);
    event CommitOwnership(address admin);
    event Deposit(address indexed provider, uint256 value);
    event UpdateLiquidityLimit(
        address user, uint256 original_balance, uint256 original_supply, uint256 working_balance, uint256 working_supply
    );
    event Withdraw(address indexed provider, uint256 value);

    function add_reward(address _reward_token, address _distributor) external;
    function approve(address _spender, uint256 _value) external returns (bool);
    function claim_rewards() external;
    function claim_rewards(address _addr) external;
    function claim_rewards(address _addr, address _receiver) external;
    function claimable_tokens(address addr) external returns (uint256);
    function decreaseAllowance(address _spender, uint256 _subtracted_value) external returns (bool);
    function deposit(uint256 _value) external;
    function deposit(uint256 _value, address _addr) external;
    function deposit(uint256 _value, address _addr, bool _claim_rewards) external;
    function deposit_reward_token(address _reward_token, uint256 _amount) external;
    function increaseAllowance(address _spender, uint256 _added_value) external returns (bool);
    function initialize(address _lp_token) external;
    function kick(address addr) external;
    function set_killed(bool _is_killed) external;
    function set_reward_distributor(address _reward_token, address _distributor) external;
    function set_rewards_receiver(address _receiver) external;
    function transfer(address _to, uint256 _value) external returns (bool);
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
    function user_checkpoint(address addr) external returns (bool);
    function withdraw(uint256 _value) external;
    function withdraw(uint256 _value, bool _claim_rewards) external;
    function allowance(address arg0, address arg1) external view returns (uint256);
    function balanceOf(address arg0) external view returns (uint256);
    function claimable_reward(address _user, address _reward_token) external view returns (uint256);
    function claimed_reward(address _addr, address _token) external view returns (uint256);
    function decimals() external view returns (uint256);
    function factory() external view returns (address);
    function future_epoch_time() external view returns (uint256);
    function inflation_rate() external view returns (uint256);
    function integrate_checkpoint() external view returns (uint256);
    function integrate_checkpoint_of(address arg0) external view returns (uint256);
    function integrate_fraction(address arg0) external view returns (uint256);
    function integrate_inv_supply(uint256 arg0) external view returns (uint256);
    function integrate_inv_supply_of(address arg0) external view returns (uint256);
    function is_killed() external view returns (bool);
    function lp_token() external view returns (address);
    function name() external view returns (string memory);
    function period() external view returns (int128);
    function period_timestamp(uint256 arg0) external view returns (uint256);
    function reward_count() external view returns (uint256);
    function reward_data(address arg0)
        external
        view
        returns (
            address token,
            address distributor,
            uint256 period_finish,
            uint256 rate,
            uint256 last_update,
            uint256 integral
        );
    function reward_integral_for(address arg0, address arg1) external view returns (uint256);
    function reward_tokens(uint256 arg0) external view returns (address);
    function rewards_receiver(address arg0) external view returns (address);
    function symbol() external view returns (string memory);
    function totalSupply() external view returns (uint256);
    function working_balances(address arg0) external view returns (uint256);
    function working_supply() external view returns (uint256);
    function admin() external view returns (address);
}
"
    },
    "src/ProtocolContext.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {IModuleManager} from "@interfaces/safe/IModuleManager.sol";

import {IAccountant} from "src/interfaces/IAccountant.sol";
import {IProtocolController} from "src/interfaces/IProtocolController.sol";

/// @title ProtocolContext.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice Base contract providing shared protocol configuration and transaction execution.
contract ProtocolContext {
    //////////////////////////////////////////////////////
    // --- IMMUTABLES
    //////////////////////////////////////////////////////

    /// @notice Unique identifier for the protocol (e.g., keccak256("CURVE") for Curve)
    /// @dev Used to look up protocol-specific components in ProtocolController
    bytes4 public immutable PROTOCOL_ID;

    /// @notice The locker contract that holds and manages protocol tokens (e.g., veCRV)
    /// @dev On L2s, this may be the same as GATEWAY when no separate locker exists
    address public immutable LOCKER;

    /// @notice Safe multisig that owns the locker and executes privileged operations
    /// @dev All protocol interactions go through this gateway for security
    address public immutable GATEWAY;

    /// @notice The accountant responsible for tracking rewards and user balances
    /// @dev Retrieved from ProtocolController during construction
    address public immutable ACCOUNTANT;

    /// @notice The main reward token for this protocol (e.g., CRV for Curve)
    /// @dev Retrieved from the accountant's configuration
    address public immutable REWARD_TOKEN;

    /// @notice Reference to the central registry for protocol components
    IProtocolController public immutable PROTOCOL_CONTROLLER;

    //////////////////////////////////////////////////////
    // --- ERRORS
    //////////////////////////////////////////////////////

    /// @notice Error thrown when a required address is zero
    error ZeroAddress();

    /// @notice Error thrown when a protocol ID is zero
    error InvalidProtocolId();

    //////////////////////////////////////////////////////
    // --- CONSTRUCTOR
    //////////////////////////////////////////////////////

    /// @notice Initializes protocol configuration that all inheriting contracts will use
    /// @dev Retrieves accountant and reward token from ProtocolController for consistency
    /// @param _protocolId The protocol identifier (must match registered protocol in controller)
    /// @param _protocolController The protocol controller contract address
    /// @param _locker The locker contract address (pass address(0) for L2s where gateway acts as locker)
    /// @param _gateway The gateway contract address (Safe multisig)
    /// @custom:throws ZeroAddress If protocol controller or gateway is zero
    /// @custom:throws InvalidProtocolId If protocol ID is empty
    constructor(bytes4 _protocolId, address _protocolController, address _locker, address _gateway) {
        require(_protocolController != address(0) && _gateway != address(0), ZeroAddress());
        require(_protocolId != bytes4(0), InvalidProtocolId());

        GATEWAY = _gateway;
        PROTOCOL_ID = _protocolId;
        ACCOUNTANT = IProtocolController(_protocolController).accountant(_protocolId);
        REWARD_TOKEN = IAccountant(ACCOUNTANT).REWARD_TOKEN();
        PROTOCOL_CONTROLLER = IProtocolController(_protocolController);

        // L2 optimization: Gateway can act as both transaction executor and token holder
        if (_locker == address(0)) {
            LOCKER = GATEWAY;
        } else {
            LOCKER = _locker;
        }
    }

    //////////////////////////////////////////////////////
    // --- INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////

    /// @notice Executes privileged transactions through the Safe module system
    /// @dev Handles two execution patterns:
    ///      - Mainnet: Gateway -> Locker -> Target (locker holds funds and executes)
    ///      - L2: Gateway acts as locker and executes directly on target
    /// @param target The address of the contract to interact with
    /// @param data The calldata to send to the target
    /// @return success Whether the transaction executed successfully
    function _executeTransaction(address target, bytes memory data) internal returns (bool success) {
        if (LOCKER == GATEWAY) {
            // L2 pattern: Gateway holds funds and executes directly
            success = IModuleManager(GATEWAY).execTransactionFromModule(target, 0, data, IModuleManager.Operation.Call);
        } else {
            // Mainnet pattern: Gateway instructs locker (which holds funds) to execute
            // The locker contract has the necessary approvals and balances
            success = IModuleManager(GATEWAY)
                .execTransactionFromModule(
                    LOCKER,
                    0,
                    abi.encodeWithSignature("execute(address,uint256,bytes)", target, 0, data),
                    IModuleManager.Operation.Call
                );
        }
    }

    /// @notice Executes privileged transactions through the Safe module system
    /// @dev Handles two execution patterns:
    ///      - Mainnet: Gateway -> Locker -> Target (locker holds funds and executes)
    ///      - L2: Gateway acts as locker and executes directly on target
    /// @param target The address of the contract to interact with
    /// @param data The calldata to send to the target
    /// @return success Whether the transaction executed successfully
    function _executeTransactionReturnData(address target, bytes memory data)
        internal
        returns (bool success, bytes memory returnData)
    {
        if (LOCKER == GATEWAY) {
            // L2 pattern: Gateway holds funds and executes directly
            (success, returnData) = IModuleManager(GATEWAY)
                .execTransactionFromModuleReturnData(target, 0, data, IModuleManager.Operation.Call);
        } else {
            // Mainnet pattern: Gateway instructs locker (which holds funds) to execute
            // The locker contract has the necessary approvals and balances
            (success, returnData) = IModuleManager(GATEWAY)
                .execTransactionFromModuleReturnData(
                    LOCKER,
                    0,
                    abi.encodeWithSignature("execute(address,uint256,bytes)", target, 0, data),
                    IModuleManager.Operation.Call
                );
        }
    }
}
"
    },
    "src/interfaces/IStrategy.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import "src/interfaces/IAllocator.sol";

interface IStrategy {
    /// @notice The policy for harvesting rewards.
    enum HarvestPolicy {
        CHECKPOINT,
        HARVEST
    }

    struct PendingRewards {
        uint128 feeSubjectAmount;
        uint128 totalAmount;
    }

    function deposit(IAllocator.Allocation calldata allocation, HarvestPolicy policy)
        external
        returns (PendingRewards memory pendingRewards);
    function withdraw(IAllocator.Allocation calldata allocation, HarvestPolicy policy, address receiver)
        external
        returns (PendingRewards memory pendingRewards);

    function balanceOf(address gauge) external view returns (uint256 balance);

    function harvest(address gauge, bytes calldata extraData) external returns (PendingRewards memory pendingRewards);
    function flush() external;

    function shutdown(address gauge) external;
    function rebalance(address gauge) external;
}
"
    },
    "src/interfaces/IRewardReceiver.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

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

interface IRewardReceiver {
    function distributeRewards() external;
    function distributeRewardToken(IERC20 token) external;
    function pullRewards(address token) external returns (uint128 amount);
    function legacyRewardReceiver() external view returns (address);
}
"
    },
    "src/interfaces/IFactoryWithSidecar.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {IFactory} from 'src/interfaces/IFactory.sol';

/// @title IFactoryWithSidecar.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice Interface for factories that support Convex sidecar functionality.
interface ICurveFactory is IFactory {
    /// @notice Convex sidecar factory address
    function CONVEX_SIDECAR_FACTORY() external view returns (address);
}
"
    },
    "src/integrations/curve/ConvexSidecar.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

/// Interfaces
import {IBooster} from "@interfaces/convex/IBooster.sol";
import {IBaseRewardPool} from "@interfaces/convex/IBaseRewardPool.sol";
import {IStashTokenWrapper} from "@interfaces/convex/IStashTokenWrapper.sol";

/// OpenZeppelin
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// Project Contracts
import {Sidecar} from "src/Sidecar.sol";
import {ImmutableArgsParser} from "src/libraries/ImmutableArgsParser.sol";

/// @title ConvexSidecar.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice Sidecar for managing Convex rewards and deposits.
contract ConvexSidecar is Sidecar {
    using SafeERC20 for IERC20;
    using ImmutableArgsParser for address;

    /// @notice The bytes4 ID of the Curve protocol
    /// @dev Used to identify the Curve protocol in the registry
    bytes4 private constant CURVE_PROTOCOL_ID = bytes4(keccak256("CURVE"));

    //////////////////////////////////////////////////////
    // ---  IMPLEMENTATION CONSTANTS
    //////////////////////////////////////////////////////

    /// @notice Convex Reward Token address.
    IERC20 public immutable CVX;

    /// @notice Convex Booster address.
    address public immutable BOOSTER;

    /// @notice Thrown when the reward receiver is not set in the protocol controller.
    error RewardReceiverNotSet();

    //////////////////////////////////////////////////////
    // --- ISIDECAR CLONE IMMUTABLES
    //////////////////////////////////////////////////////

    /// @notice Staking token address.
    function asset() public view override returns (IERC20 _asset) {
        return IERC20(address(this).readAddress(0));
    }

    /// @notice Curve gauge associated with the sidecar.
    function gauge() public view returns (address _gauge) {
        return address(this).readAddress(20);
    }

    function rewardReceiver() public view override returns (address _rewardReceiver) {
        _rewardReceiver = PROTOCOL_CONTROLLER.rewardReceiver(gauge());
        if (_rewardReceiver == address(0)) revert RewardReceiverNotSet();
    }

    //////////////////////////////////////////////////////
    // --- CONVEX CLONE IMMUTABLES
    //////////////////////////////////////////////////////

    /// @notice Staking Convex LP contract address.
    function baseRewardPool() public view returns (IBaseRewardPool _baseRewardPool) {
        return IBaseRewardPool(address(this).readAddress(40));
    }

    /// @notice Identifier of the pool on Convex.
    function pid() public view returns (uint256 _pid) {
        return address(this).readUint256(60);
    }

    //////////////////////////////////////////////////////
    // --- CONSTRUCTOR
    //////////////////////////////////////////////////////

    constructor(address _accountant, address _protocolController, address _cvx, address _booster)
        Sidecar(CURVE_PROTOCOL_ID, _accountant, _protocolController)
    {
        CVX = IERC20(_cvx);
        BOOSTER = _booster;
    }

    //////////////////////////////////////////////////////
    // --- INITIALIZATION
    //////////////////////////////////////////////////////

    /// @notice Initialize the contract by approving the ConvexCurve booster to spend the LP token.
    function _initialize() internal override {
        require(asset().allowance(address(this), address(BOOSTER)) == 0, AlreadyInitialized());

        asset().forceApprove(address(BOOSTER), type(uint256).max);
    }

    //////////////////////////////////////////////////////
    // --- ISIDECAR OPERATIONS OVERRIDE
    //////////////////////////////////////////////////////

    /// @notice Deposit LP token into Convex.
    /// @param amount Amount of LP token to deposit.
    /// @dev The reason there's an empty address parameter is to keep flexibility for future implementations.
    /// Not all fallbacks will be minimal proxies, so we need to keep the same function signature.
    /// Only callable by the strategy.
    function _deposit(uint256 amount) internal override {
        /// Deposit the LP token into Convex and stake it (true) to receive rewards.
        IBooster(BOOSTER).deposit(pid(), amount, true);
    }

    /// @notice Withdraw LP token from Convex.
    /// @param amount Amount of LP token to withdraw.
    /// @param receiver Address to receive the LP token.
    function _withdraw(uint256 amount, address receiver) internal override {
        /// Withdraw from Convex gauge without claiming rewards (false).
        baseRewardPool().withdrawAndUnwrap(amount, false);

        /// Send the LP token to the receiver.
        asset().safeTransfer(receiver, amount);
    }

    /// @notice Claim rewards from Convex.
    /// @return rewardTokenAmount Amount of reward token claimed.
    function _claim() internal override returns (uint256 rewardTokenAmount) {
        /// Claim rewardToken.
        baseRewardPool().getReward(address(this), false);

        rewardTokenAmount = REWARD_TOKEN.balanceOf(address(this));

        if (rewardTokenAmount > 0) {
            /// Send the reward token to the accountant.
            REWARD_TOKEN.safeTransfer(ACCOUNTANT, rewardTokenAmount);
        }
    }

    /// @notice Get the balance of the LP token on Convex held by this contract.
    function balanceOf() public view override returns (uint256) {
        return baseRewardPool().balanceOf(address(this));
    }

    /// @notice Get the reward tokens from the base reward pool.
    /// @return Array of all extra reward tokens.
    function getRewardTokens() public view override returns (address[] memory) {
        // Check if there is extra rewards
        uint256 extraRewardsLength = baseRewardPool().extraRewardsLength();

        address[] memory tokens = new address[](extraRewardsLength);

        address _token;
        for (uint256 i; i < extraRewardsLength;) {
            /// Get the address of the virtual balance pool.
            _token = baseRewardPool().extraRewards(i);

            tokens[i] = IBaseRewardPool(_token).rewardToken();

            /// For PIDs greater than 150, the virtual balance pool also has a wrapper.
            /// So we need to get the token from the wrapper.
            /// Try catch because pid 151 case is only on Mainnet, not on L2s.
            /// More: https://docs.convexfinance.com/convexfinanceintegration/baserewardpool
            try IStashTokenWrapper(tokens[i]).token() returns (address _t) {
                tokens[i] = _t;
            } catch {}

            unchecked {
                ++i;
            }
        }

        return tokens;
    }

    /// @notice Get the amount of reward token earned by the strategy.
    /// @return The amount of reward token earned by the strategy.
    function getPendingRewards() public view override returns (uint256) {
        return baseRewardPool().earned(address(this)) + REWARD_TOKEN.balanceOf(address(this));
    }

    //////////////////////////////////////////////////////
    // --- EXTRA CONVEX OPERATIONS
    //////////////////////////////////////////////////////

    function claimExtraRewards() external {
        address[] memory extraRewardTokens = getRewardTokens();

        /// It'll claim rewardToken but we'll leave it here for clarity until the claim() function is called by the strategy.
        baseRewardPool().getReward(address(this), true);

        /// Send the reward token to the reward receiver.
        address receiver = rewardReceiver();
        uint256 balance = CVX.balanceOf(address(this));
        if (balance > 0) {
            CVX.safeTransfer(receiver, balance);
        }

        /// Handle the extra reward tokens.
        for (uint256 i = 0; i < extraRewardTokens.length;) {
            uint256 _balance = IERC20(extraRewardTokens[i]).balanceOf(address(this));
            if (_balance > 0) {
                /// Send the whole balance to the reward receiver.
                IERC20(extraRewardTokens[i]).safeTransfer(receiver, _balance);
            }

            unchecked {
                ++i;
            }
        }
    }
}
"
    },
    "src/integrations/curve/OnlyBoostAllocator.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Allocator} from "src/Allocator.sol";
import {IBalanceProvider} from "src/interfaces/IBalanceProvider.sol";
import {ISidecar} from "src/interfaces/ISidecar.sol";
import {IConvexSidecarFactory} from "src/interfaces/IConvexSidecarFactory.sol";


/// @title OnlyBoostAllocator.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice Calculates the optimal LP token allocation for Stake DAO Locker and Convex.
contract OnlyBoostAllocator is Allocator {
    using Math for uint256;

    /// @notice Address of the Curve Boost Delegation V3 contract
    address public immutable BOOST_PROVIDER;

    /// @notice Address of the Convex Boost Holder contract
    address public immutable CONVEX_BOOST_HOLDER;

    /// @notice Address of the Convex Sidecar Factory contract
    IConvexSidecarFactory public immutable CONVEX_SIDECAR_FACTORY;

    /// @notice Gauges forced to allocate 100% to the locker (used during migrations).
    mapping(address => bool) public lockerOnly;

    /// @notice Emitted when the locker-only override is toggled for a gauge.
    event LockerOnlyUpdated(address indexed gauge, bool lockerOnly);

    /// @notice Initializes the OnlyBoostAllocator contract
    /// @param _locker Address of the Stake DAO Liquidity Locker
    /// @param _gateway Address of the gateway contract
    /// @param _convexSidecarFactory Address of the Convex Sidecar Factory contract
    constructor(
        address _locker,
        address _gateway,
        address _convexSidecarFactory,
        address _boostProvider,
        address _convexBoostHolder
    ) Allocator(_locker, _gateway) {
        BOOST_PROVIDER = _boostProvider;
        CONVEX_BOOST_HOLDER = _convexBoostHolder;
        CONVEX_SIDECAR_FACTORY = IConvexSidecarFactory(_convexSidecarFactory);
    }

    /// @notice Enables or disables sidecar allocations for a gauge.
    /// @dev Need to rebalance the allocator to update the allocation targets, else the deposit/withdrawal will fail.
    function setLockerOnly(address gauge, bool value) external onlyLocker {
        lockerOnly[gauge] = value;
        emit LockerOnlyUpdated(gauge, value);
    }

    //////////////////////////////////////////////////////
    // --- DEPOSIT ALLOCATION
    //////////////////////////////////////////////////////

    /// @inheritdoc Allocator
    function getDepositAllocation(address asset, address gauge, uint256 amount)
        public
        view
        override
        returns (Allocation memory alloc)
    {
        // 1. Resolve the sidecar for the gauge.
        address sidecar = CONVEX_SIDECAR_FACTORY.getSidecar(gauge);

        if (sidecar == address(0)) {
            return super.getDepositAllocation(asset, gauge, amount);
        }

        // 3. Prepare targets and amounts containers.
        alloc.asset = asset;
        alloc.gauge = gauge;
        alloc.targets = _targets(sidecar);
        alloc.amounts = _pair(0, 0);

        if (lockerOnly[gauge]) {
            alloc.amounts[1] = amount;
            return alloc;
        }

        // 4. Fetch current balances.
        uint256 balanceOfLocker = IBalanceProvider(gauge).balanceOf(LOCKER);

        // 5. Get the optimal balance based on Convex balance and veBoost ratio.
        uint256 optimalBalanceOfLocker = getOptimalLockerBalance(gauge);

        // 6. Calculate the amount of lps to deposit into the locker.
        alloc.amounts[1] =
            optimalBalanceOfLocker > balanceOfLocker ? Math.min(optimalBalanceOfLocker - balanceOfLocker, amount) : 0;

        // 7. Calculate the amount of lps to deposit into the sidecar.
        alloc.amounts[0] = amount - alloc.amounts[1];
    }

    //////////////////////////////////////////////////////
    // --- WITHDRAWAL ALLOCATION
    //////////////////////////////////////////////////////

    /// @inheritdoc Allocator
    function getWithdrawalAllocation(address asset, address gauge, uint256 amount)
        public
        view
        override
        returns (Allocation memory alloc)
    {
        // 1. Resolve the sidecar.
        address sidecar = CONVEX_SIDECAR_FACTORY.getSidecar(gauge);

        // 2. Fallback to base allocator if none.
        if (sidecar == address(0)) {
            return super.getWithdrawalAllocation(asset, gauge, amount);
        }

        // 3. Prepare return struct.
        alloc.asset = asset;
        alloc.gauge = gauge;
        alloc.targets = _targets(sidecar);
        alloc.amounts = _pair(0, 0);

        // 4. Current balances.
        uint256 balanceOfSidecar = ISidecar(sidecar).balanceOf();
        uint256 balanceOfLocker = IBalanceProvider(gauge).balanceOf(LOCKER);

        // 5. Calculate the optimal amount of lps that must be held by the locker.
        uint256 optimalBalanceOfLocker = getOptimalLockerBalance(gauge);

        // 6. Calculate the total balance.
        uint256 totalBalance = balanceOfSidecar + balanceOfLocker;

        if (lockerOnly[gauge]) {
            if (totalBalance <= amount) {
                alloc.amounts[0] = balanceOfSidecar;
                alloc.amounts[1] = balanceOfLocker;
            } else {
                alloc.amounts[0] = Math.min(amount, balanceOfSidecar);
                uint256 remaining = amount - alloc.amounts[0];
                alloc.amounts[1] = Math.min(remaining, balanceOfLocker);
            }
            return alloc;
        }

        // 7. Adjust the withdrawal based on the optimal amount for Stake DAO
        if (totalBalance <= amount) {
            // 7a. If the total balance is less than or equal to the withdrawal amount, withdraw everything
            alloc.amounts[0] = balanceOfSidecar;
            alloc.amounts[1] = balanceOfLocker;
        } else if (optimalBalanceOfLocker >= balanceOfLocker) {
            // 7b. If Stake DAO balance is below optimal, prioritize withdrawing from Convex
            alloc.amounts[0] = Math.min(amount, balanceOfSidecar);
            alloc.amounts[1] = amount > alloc.amounts[0] ? amount - alloc.amounts[0] : 0;
        } else {
            // 7c. If Stake DAO is above optimal, prioritize withdrawing from Stake DAO,
            //     but only withdraw as much as needed to bring the balance down to the optimal amount.
            alloc.amounts[1] = Math.min(amount, balanceOfLocker - optimalBalanceOfLocker);
            alloc.amounts[0] = amount > alloc.amounts[1] ? Math.min(amount - alloc.amounts[1], balanceOfSidecar) : 0;

            // 7d. If there is still more to withdraw, withdraw the rest from Stake DAO.
            if (amount > alloc.amounts[0] + alloc.amounts[1]) {
                alloc.amounts[1] += amount - alloc.amounts[0] - alloc.amounts[1];
            }
        }
    }

    //////////////////////////////////////////////////////
    // --- REBALANCE ALLOCATION
    //////////////////////////////////////////////////////

    /// @inheritdoc Allocator
    function getRebalancedAllocation(address asset, address gauge, uint256 totalBalance)
        public
        view
        override
        returns (Allocation memory alloc)
    {
        // 1. Resolve sidecar.
        address sidecar = CONVEX_SIDECAR_FACTORY.getSidecar(gauge);
        if (sidecar == address(0)) {
            return super.getRebalancedAllocation(asset, gauge, totalBalance);
        }


        // 2. Prepare struct.
        alloc.asset = asset;
        alloc.gauge = gauge;
        alloc.targets = _targets(sidecar);
        alloc.amounts = _pair(0, 0);


        if (lockerOnly[gauge]) {
            alloc.amounts[0] = 0;
            alloc.amounts[1] = totalBalance;
        }
        else {
            // 3. For rebalancing, we still want to match the optimal balance based on Convex holdings
            // This ensures we maintain the boost-maximizing ratio
            uint256 optimalLockerBalance = getOptimalLockerBalance(gauge);

            // Cap the locker amount to the total balance available
            alloc.amounts[1] = Math.min(optimalLockerBalance, totalBalance);
            alloc.amounts[0] = totalBalance - alloc.amounts[1];

        }
    }

    //////////////////////////////////////////////////////
    // --- VIEW HELPER FUNCTIONS
    //////////////////////////////////////////////////////

    /// @inheritdoc Allocator
    function getAllocationTargets(address gauge) public view override returns (address[] memory) {
        address sidecar = CONVEX_SIDECAR_FACTORY.getSidecar(gauge);
        if (sidecar == address(0)) {
            return super.getAllocationTargets(gauge);
        }

        return _targets(sidecar);
    }

    /// @notice Returns the optimal amount of LP token that must be held by Stake DAO Locker
    /// @dev Calculates the optimal balance to maximize boost efficiency
    /// @param gauge Address of the Curve gauge
    /// @return balanceOfLocker Optimal amount of LP token that should be held by Stake DAO Locker
    function getOptimalLockerBalance(address gauge) public view returns (uint256 balanceOfLocker) {
        // 1. Get the balance of veBoost on Stake DAO and Convex
        uint256 veBoostOfLocker = IBalanceProvider(BOOST_PROVIDER).balanceOf(LOCKER);
        uint256 veBoostOfConvex = IBalanceProvider(BOOST_PROVIDER).balanceOf(CONVEX_BOOST_HOLDER);

        // 2. Get the balance of the liquidity gauge on Convex
        uint256 balanceOfConvex = IBalanceProvider(gauge).balanceOf(CONVEX_BOOST_HOLDER);

        // 3. If there is no balance of Convex or no veBoost on Convex, return 0
        if (balanceOfConvex == 0 || veBoostOfConvex == 0) return 0;

        // 4. Compute the optimal balance for Stake DAO based on veBoost ratio
        // This ensures Stake DAO gets LP tokens proportional to its veBoost advantage
        balanceOfLocker = balanceOfConvex.mulDiv(veBoostOfLocker, veBoostOfConvex);
    }

    //////////////////////////////////////////////////////
    // --- HELPER FUNCTIONS
    //////////////////////////////////////////////////////

    /// @dev Returns the pair `[sidecar, LOCKER]` used by allocation targets.
    function _targets(address sidecar) private view returns (address[] memory arr) {
        arr = new address[](2);
        arr[0] = sidecar;
        arr[1] = LOCKER;
    }

    /// @dev Utility to allocate a two‑element uint256 array.
    function _pair(uint256 a0, uint256 a1) private pure returns (uint256[] memory arr) {
        arr = new uint256[](2);
        arr[0] = a0;
        arr[1] = a1;
    }
}
"
    },
    "src/integrations/curve/ConvexSidecarFactory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {SidecarFactory} from "src/SidecarFactory.sol";
import {IBooster} from "@interfaces/convex/IBooster.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ConvexSidecar} from "src/integrations/curve/ConvexSidecar.sol";

/// @title ConvexSidecarFactory.
/// @author Stake DAO
/// @custom:github @stake-dao
/// @custom:contact contact@stakedao.org

/// @notice Factory contract for deploying ConvexSidecar instances.
contract ConvexSidecarFactory is SidecarFactory {
    /// @notice The bytes4 ID of the Convex protocol
    /// @dev Used to identify the Convex protocol in the registry
    bytes4 private constant CURVE_PROTOCOL_ID = bytes4(keccak256("CURVE"));

    /// @notice Convex Booster contract address
    address public immutable BOOSTER;

    address public constant OLD_SIDECAR_FACTORY = 0x7Fa7fDb80b17f502C323D14Fa654a1e56B03C592;

    /// @notice Error emitted when the pool is shutdown
    error PoolShutdown();

    /// @notice Error emitted when the arguments are invalid
    error InvalidArguments();

    /// @notice Error emitted when the reward receiver is not set
    error VaultNotDeployed();

      /// @notice Constructor
    /// @param _implementation Address of the sidecar implementation
    /// @param _protocolController Address of the protocol controller
    /// @param _booster Address of the Convex Booster contract
    constructor(address _implementation, address _protocolController, address _booster)
        SidecarFactory(CURVE_PROTOCOL_ID, _implementation, _protocolController)
    {
        BOOSTER = _booster;
    }

    /// @notice Convenience function to create a sidecar with a uint256 pid parameter
    /// @param pid Pool ID in Convex
    /// @return sidecar Address of the created sidecar
    function create(address gauge, uint256 pid) external returns (address sidecar) {
        bytes memory args = abi.encode(pid);
        return create(gauge, args);
    }

    /// @notice Validates the gauge and arguments for Convex
    /// @param gauge The gauge to validate
    /// @param args The arguments containing the pool ID
    function _isValidGauge(address gauge, bytes memory args) internal view override {
        require(args.length == 32, InvalidArguments());

        uint256 pid = abi.decode(args, (uint256));

        // Get the pool info from Convex
        (,, address curveGauge,,, bool isShutdown) = IBooster(BOOSTER).poolInfo(pid);

        // Ensure the pool is not shutdown
        if (isShutdown) revert PoolShutdown();

        // Ensure the gauge matches
        if (curveGauge != gauge) revert InvalidGauge();
    }

    /// @notice Creates a ConvexSidecar for a gauge
    /// @param gauge The gauge to create a sidecar for
    /// @param args The arguments containing the pool ID
    /// @return sidecarAddress Address of the created sidecar
    function _create(address gauge, bytes memory args) internal override returns (address sidecarAddress) {
        uint256 pid = abi.decode(args, (uint256));

        // Get the LP token and base reward pool from Convex
        (address lpToken,,, address baseRewardPool,,) = IBooster(BOOSTER).poolInfo(pid);

        address rewardReceiver = PROTOCOL_CONTROLLER.rewardReceiver(gauge);
        require(rewardReceiver != address(0), VaultNotDeployed());

        // Encode the immutable arguments for the clone
        bytes memory data = abi.encodePacked(lpToken, gauge, baseRewardPool, pid);

        // Create a deterministic salt based on the token and gauge
        bytes32 salt = keccak256(data);

        // Clone the implementation contract
        sidecarAddress = Clones.cloneDeterministicWithImmutableArgs(IMPLEMENTATION, data, salt);

        // Initialize the sidecar
        ConvexSidecar(sidecarAddress).initialize();

        // Set the valid allocation target
        PROTOCOL_CONTROLLER.setValidAllocationTarget(gauge, sidecarAddress);

        return sidecarAddress;
    }

    function getSidecar(address gauge) public view returns (address _sidecar) {
        _sidecar = sidecar[gauge];

        if (_sidecar == address(0)) {
            _sidecar = ConvexSidecarFactory(OLD_SIDECAR_FACTORY).sidecar(gauge);
        }
    }
}
"
    },
    "node_modules/@openzeppelin/contracts/utils/Create2.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}
"
    },
    "node_modules/@openzeppelin/contracts/utils/Errors.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}
"
    },
    "node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(add

Tags:
ERC20, ERC165, Multisig, Pausable, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory|addr:0x2c043dd5adee81c93a5b5c2ccda00de9c4cbfd46|verified:true|block:23645990|tx:0x37bc80c1331120cc2911a04cdb4c9691646171a2259062cfc5e969f7409e002c|first_check:1761324348

Submitted on: 2025-10-24 18:45:48

Comments

Log in to comment.

No comments yet.