CurveFactory

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

import {IBooster} from "@interfaces/convex/IBooster.sol";
import {IStrategy} from "@interfaces/stake-dao/IStrategy.sol";
import {ILiquidityGauge} from "@interfaces/curve/ILiquidityGauge.sol";
import {IGaugeController} from "@interfaces/curve/IGaugeController.sol";
import {CurveLocker, CurveProtocol} from "@address-book/src/CurveEthereum.sol";

import {Factory} from "src/Factory.sol";
import {IRewardVault} from "src/interfaces/IRewardVault.sol";
import {ISidecarFactory} from "src/interfaces/ISidecarFactory.sol";

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

/// @notice Factory contract for deploying Curve strategies.
contract CurveFactory is Factory {
    /// @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"));

    /// @notice Curve Gauge Controller.
    address public immutable GAUGE_CONTROLLER;

    /// @notice CVX token address.
    address public immutable CVX;

    /// @notice Address of the old strategy.
    address public immutable OLD_STRATEGY;

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

    /// @notice Convex Minimal Proxy Factory for Only Boost.
    address public immutable CONVEX_SIDECAR_FACTORY;

   


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

    /// @notice Error thrown when the convex sidecar factory is not set.
    error ConvexSidecarFactoryNotSet();

    /// @notice Event emitted when a vault is deployed.
    event VaultDeployed(address gauge, address vault, address rewardReceiver, address sidecar);


    /// @notice Constructor parameters to avoid stack too deep errors.
     struct ConstructorParams {
        address gaugeController;
        address cvx;
        address oldStrategy;
        address booster;
        address protocolController;
        address vaultImplementation;
        address rewardReceiverImplementation;
        address rewardReceiverMigrationModule;
        address rewardRouter;
        address locker;
        address gateway;
        address convexSidecarFactory;
        address registrar;
    }

    constructor(
        ConstructorParams memory params
    )
        Factory(params.protocolController, params.vaultImplementation, params.rewardReceiverImplementation, params.rewardReceiverMigrationModule,params.rewardRouter, CURVE_PROTOCOL_ID, params.locker, params.gateway, params.registrar)
    {
        GAUGE_CONTROLLER = params.gaugeController;
        CVX = params.cvx;
        OLD_STRATEGY = params.oldStrategy;
        BOOSTER = params.booster;
        CONVEX_SIDECAR_FACTORY = params.convexSidecarFactory;
    }

    /// @notice Create a new vault.
    /// @param _pid Pool id.
    function create(uint256 _pid) external returns (address vault, address rewardReceiver, address sidecar) {
        require(CONVEX_SIDECAR_FACTORY != address(0), ConvexSidecarFactoryNotSet());

        (,, address gauge,,,) = IBooster(BOOSTER).poolInfo(_pid);

        /// 1. Create the vault.
        (vault, rewardReceiver) = createVault(gauge);

        /// 2. Attach the sidecar.
        sidecar = ISidecarFactory(CONVEX_SIDECAR_FACTORY).create(gauge, abi.encode(_pid));

        /// 3. Emit the event.
        emit VaultDeployed(gauge, vault, rewardReceiver, sidecar);
    }

    function _isValidToken(address _token) internal view virtual override returns (bool) {
        /// If the token is not valid, return false.
        if (!super._isValidToken(_token)) return false;

        /// We already add CVX to the vault by default.
        if (_token == CVX) return false;

        /// If the token is available as an inflation receiver, it's not valid.
        try IGaugeController(GAUGE_CONTROLLER).gauge_types(_token) {
            return false;
        } catch {
            return true;
        }
    }

    function _isValidGauge(address _gauge) internal view virtual override returns (bool) {
      bool inController = false;
    bool isKilled = false;

    // Check if gauge is in controller
    try IGaugeController(GAUGE_CONTROLLER).gauge_types(_gauge) {
        inController = true;
    } catch {}

    // Check if gauge is killed
    try ILiquidityGauge(_gauge).is_killed() returns (bool _isKilled) {
        isKilled = _isKilled;
    } catch {}

    // Check whitelist
    bool whitelisted = REGISTRAR.isWhitelisted(PROTOCOL_ID, _gauge);

    // Final condition
    return !isKilled && (inController || whitelisted);
    }

    /// @notice Check if the gauge is shutdown in the old strategy.
    /// @dev If the gauge is shutdown, we can deploy a new strategy.
    function _isValidDeployment(address _gauge) internal view virtual override returns (bool) {
        /// We check if the gauge is deployed in the old strategy by checking if the reward distributor is not 0.
        /// We also check if the gauge is shutdown.
        return
            IStrategy(OLD_STRATEGY).rewardDistributors(_gauge) == address(0)
                || IStrategy(OLD_STRATEGY).isShutdown(_gauge);
    }

    function _getAsset(address _gauge) internal view virtual override returns (address) {
        return ILiquidityGauge(_gauge).lp_token();
    }

    function _setupRewardTokens(address _vault, address _gauge, address _rewardReceiver) internal virtual override {
        /// Add CVX to the vault if it's not already there.
        if (!IRewardVault(_vault).isRewardToken(CVX)) {
            IRewardVault(_vault).addRewardToken(CVX, _rewardReceiver);
        }

        /// Check if the gauge supports extra rewards.
        /// This function is not supported on all gauges, depending on when they were deployed.
        bytes memory data = abi.encodeWithSignature("reward_tokens(uint256)", 0);

        (bool success,) = _gauge.call(data);
        if (!success) return;

        /// Loop through the extra reward tokens.
        /// 8 is the maximum number of extra reward tokens supported by the gauges.
        uint256 periodFinish;
        address _extraRewardToken;
        for (uint8 i = 0; i < 8; i++) {
            /// Get the extra reward token address.
            _extraRewardToken = ILiquidityGauge(_gauge).reward_tokens(i);
            (,, periodFinish,,,) = ILiquidityGauge(_gauge).reward_data(_extraRewardToken);

            /// If the address is 0, it means there are no more extra reward tokens.
            if (_extraRewardToken == address(0)) break;
            /// If the reward data is not active, skip. We allow for 30 days of inactivity.
            if (periodFinish + 30 days < block.timestamp) continue;
            /// If the extra reward token is already in the vault, skip.
            if (IRewardVault(_vault).isRewardToken(_extraRewardToken)) continue;
            /// Performs checks on the extra reward token.
            /// Checks like if the token is also an lp token that can be staked in the locker, these tokens are not supported.
            if (_isValidToken(_extraRewardToken)) {
                /// Then we add the extra reward token to the reward distributor through the strategy.
                IRewardVault(_vault).addRewardToken(_extraRewardToken, _rewardReceiver);
            }
        }
    }

    function _setRewardReceiver(address _gauge, address _rewardReceiver) internal override {
        /// Set _rewardReceiver as the reward receiver on the gauge.
        bytes memory data = abi.encodeWithSignature("set_rewards_receiver(address)", _rewardReceiver);
        require(_executeTransaction(_gauge, data), SetRewardReceiverFailed());
    }

    function _initializeVault(address, address _asset, address _gauge) internal override {
        /// Initialize the vault.
        /// We need to approve the asset to the gauge using the Locker.
        bytes memory data = abi.encodeWithSignature("approve(address,uint256)", _gauge, type(uint256).max);

        /// Execute the transaction.
        require(_executeTransaction(_asset, data), ApproveFailed());
    }
}
"
    },
    "node_modules/@stake-dao/interfaces/src/interfaces/convex/IBooster.sol": {
      "content": "/// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

interface IBooster {
    function poolLength() external view returns (uint256);

    function poolInfo(uint256 pid)
        external
        view
        returns (address lpToken, address token, address gauge, address crvRewards, address stash, bool shutdown);

    function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns (bool);

    function earmarkRewards(uint256 _pid) external returns (bool);

    function depositAll(uint256 _pid, bool _stake) external returns (bool);

    function withdraw(uint256 _pid, uint256 _amount) external returns (bool);

    function claimRewards(uint256 _pid, address gauge) external returns (bool);
}
"
    },
    "node_modules/@stake-dao/interfaces/src/interfaces/stake-dao/IStrategy.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

interface IStrategy {
    function locker() external view returns (address);

    function deposit(address _token, uint256 amount) external;
    function withdraw(address _token, uint256 amount) external;

    function claimProtocolFees() external;
    function claimNativeRewards() external;
    function harvest(address _asset, bool _distributeSDT, bool _claimExtra) external;
    function harvest(address _asset, bool _distributeSDT, bool _claimExtra, bool) external;

    function rewardDistributors(address _gauge) external view returns (address);
    function isShutdown(address _gauge) external view returns (bool);
    function setShutdownMode(uint8 _shutdownMode) external;

    function feeDistributor() external view returns (address);

    /// Factory functions
    function toggleVault(address vault) external;
    function setGauge(address token, address gauge) external;
    function setLGtype(address gauge, uint256 gaugeType) external;
    function addRewardToken(address _token, address _rewardDistributor) external;
    function acceptRewardDistributorOwnership(address rewardDistributor) external;
    function setRewardDistributor(address gauge, address rewardDistributor) external;
    function addRewardReceiver(address gauge, address rewardReceiver) external;

    // Governance
    function setAccumulator(address newAccumulator) external;
    function setFeeRewardToken(address newFeeRewardToken) external;
    function setFeeDistributor(address newFeeDistributor) external;
    function setFactory(address newFactory) external;
    function governance() external view returns (address);
}
"
    },
    "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);
}
"
    },
    "node_modules/@stake-dao/interfaces/src/interfaces/curve/IGaugeController.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

interface IGaugeController {
    struct VotedSlope {
        uint256 slope;
        uint256 power;
        uint256 end;
    }

    struct Points {
        uint256 bias;
        uint256 slope;
    }

    event AddType(string name, int128 type_id);
    event NewGauge(address addr, int128 gauge_type, uint256 weight);
    event NewGaugeWeight(address gauge_address, uint256 time, uint256 weight, uint256 total_weight);
    event NewTypeWeight(int128 type_id, uint256 time, uint256 weight, uint256 total_weight);
    event VoteForGauge(uint256 time, address user, address gauge_addr, uint256 weight);

    function add_gauge(address addr, int128 gauge_type) external;
    function add_gauge(address addr, int128 gauge_type, uint256 weight) external;
    function add_type(string calldata _name) external;
    function add_type(string calldata _name, uint256 weight) external;
    function change_gauge_weight(address addr, uint256 weight) external;
    function change_type_weight(int128 type_id, uint256 weight) external;
    function checkpoint() external;
    function checkpoint_gauge(address addr) external;
    function gauge_relative_weight_write(address addr) external returns (uint256);
    function gauge_relative_weight_write(address addr, uint256 time) external returns (uint256);
    function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external;
    function vote_for_many_gauge_weights(address[8] calldata _gauge_addrs, uint256[8] calldata _user_weight) external;
    function admin() external view returns (address);
    function gauge_exists(address _addr) external view returns (bool);
    function gauge_relative_weight(address addr) external view returns (uint256);
    function gauge_relative_weight(address addr, uint256 time) external view returns (uint256);
    function gauge_type_names(int128 arg0) external view returns (string memory);
    function gauge_types(address _addr) external view returns (int128);
    function gauges(uint256 arg0) external view returns (address);
    function get_gauge_weight(address addr) external view returns (uint256);
    function get_total_weight() external view returns (uint256);
    function get_type_weight(int128 type_id) external view returns (uint256);
    function get_weights_sum_per_type(int128 type_id) external view returns (uint256);
    function last_user_vote(address arg0, address arg1) external view returns (uint256);
    function n_gauge_types() external view returns (int128);
    function n_gauges() external view returns (int128);
    function points_sum(int128 arg0, uint256 arg1) external view returns (Points memory);
    function points_total(uint256 arg0) external view returns (uint256);
    function points_type_weight(int128 arg0, uint256 arg1) external view returns (uint256);
    function points_weight(address arg0, uint256 arg1) external view returns (Points memory);
    function time_sum(uint256 arg0) external view returns (uint256);
    function time_total() external view returns (uint256);
    function time_type_weight(uint256 arg0) external view returns (uint256);
    function time_weight(address arg0) external view returns (uint256);
    function token() external view returns (address);
    function vote_user_power(address arg0) external view returns (uint256);
    function vote_user_slopes(address arg0, address arg1) external view returns (VotedSlope memory);
    function voting_escrow() external view returns (address);
}
"
    },
    "node_modules/@stake-dao/address-book/src/CurveEthereum.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

library CurveProtocol {
    address internal constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52;
    address internal constant VECRV = 0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2;
    address internal constant CRV_USD = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E;
    address internal constant SD_VE_CRV = 0x478bBC744811eE8310B461514BDc29D03739084D;

    address internal constant FEE_DISTRIBUTOR = 0xD16d5eC345Dd86Fb63C6a9C43c517210F1027914;
    address internal constant GAUGE_CONTROLLER = 0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB;
    address internal constant SMART_WALLET_CHECKER = 0xca719728Ef172d0961768581fdF35CB116e0B7a4;
    address internal constant CURVE_REGISTRY = 0xc522A6606BBA746d7960404F22a3DB936B6F4F50;

    address internal constant VOTING_APP_OWNERSHIP = 0xE478de485ad2fe566d49342Cbd03E49ed7DB3356;
    address internal constant VOTING_APP_PARAMETER = 0xBCfF8B0b9419b9A88c44546519b1e909cF330399;
    address internal constant MINTER = 0xd061D61a4d941c39E5453435B6345Dc261C2fcE0;
    address internal constant VE_BOOST = 0xD37A6aa3d8460Bd2b6536d608103D880695A23CD;

    // Convex
    address internal constant CONVEX_PROXY = 0x989AEb4d175e16225E39E87d0D97A3360524AD80;
    address internal constant CONVEX_BOOSTER = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31;
    address internal constant CONVEX_TOKEN = 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B; // CVX

    address internal constant META_REGISTRY = 0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC;
}

library CurveLocker {
    address internal constant TOKEN = 0xD533a949740bb3306d119CC777fa900bA034cd52;
    address internal constant SDTOKEN = 0xD1b5651E55D4CeeD36251c61c50C889B36F6abB5;
    address internal constant ASDTOKEN = 0x43E54C2E7b3e294De3A155785F52AB49d87B9922;
    address internal constant ASDTOKEN_ADAPTER = 0x4e8DA27Fa7F109565De6FdB813D5AA1A6F73c75f;
    address internal constant SYASDTOKEN = 0x18C11b1DC74cAB82AD18d5034FDe93FE90a41D99;
    address internal constant LOCKER = 0x52f541764E6e90eeBc5c21Ff570De0e2D63766B6;
    address internal constant DEPOSITOR = 0xa50CB9dFFcc740EE6b6f2D4B3CBc3a876b28c335;
    address internal constant GAUGE = 0x7f50786A0b15723D741727882ee99a0BF34e3466;
    address internal constant ACCUMULATOR = 0x11F78501e6b0cbc5DE4c7e6BBabaACdb973eb4Cd;
    address internal constant VOTER = 0xb118fbE8B01dB24EdE7E87DFD19693cfca13e992;

    address internal constant STRATEGY = 0x69D61428d089C2F35Bf6a472F540D0F82D1EA2cd;
    address internal constant FACTORY = 0xDC9718E7704f10DB1aFaad737f8A04bcd14C20AA;
    address internal constant VE_BOOST_DELEGATION = 0xe1F9C8ebBC80A013cAf0940fdD1A8554d763b9cf;
}

library CurveVotemarket {
    address internal constant PLATFORM = 0x0000000895cB182E6f983eb4D8b4E0Aa0B31Ae4c;
    address internal constant CURVE_CONVEX_LOCKER_VM_RECIPIENT = 0x0000000095310137125f82f37FBe5D2F99279947;
    address internal constant CURVE_STAKE_DAO_LOCKER_VM_RECIPIENT = 0x0000000014814b037cF4a091FE00cbA2DeFc6115;
}

library CurveStrategy {
    address internal constant ACCOUNTANT = 0x93b4B9bd266fFA8AF68e39EDFa8cFe2A62011Ce0;
    address internal constant PROTOCOL_TIMELOCK = 0xb27afc7844988948FBd6210AeF4E1362bC2d8E6a;
    address internal constant PROTOCOL_CONTROLLER = 0x2d8BcE1FaE00a959354aCD9eBf9174337A64d4fb;
    address internal constant GATEWAY = 0xe5d6D047DF95c6627326465cB27B64A8b77A8b91;

    address internal constant FEE_RECEIVER = 0x60136fefE23D269aF41aB72DE483D186dC4318D6;

    address internal constant STRATEGY = 0x7D0775442d5961AE7090e4EC6C76180e8EEeEf54;

    address internal constant CONVEX_SIDECAR_IMPLEMENTATION = 0x66c3ce4718A39d44CE2430eB3E8B8d43c18bA1fa;
    address internal constant CONVEX_SIDECAR_FACTORY = 0x7Fa7fDb80b17f502C323D14Fa654a1e56B03C592;

    address internal constant FACTORY = 0x37B015FA4Ba976c57E8e3A0084288d9DcEA06003;
    address internal constant ALLOCATOR = 0x6Dbf307916Ae9c47549AbaF11Cb476252a14Ee9D;

    address internal constant REWARD_VAULT_IMPLEMENTATION = 0x74D8dd40118B13B210D0a1639141cE4458CAe0c0;
    address internal constant REWARD_RECEIVER_IMPLEMENTATION = 0x4E35037263f75F9fFE191B5f9B5C7cd0c3169019;

    address internal constant ROUTER = 0xc3a6CfC4c8112fBfd77f0d095a0eE2f2F4505Eef;
    address internal constant ROUTER_MODULE_DEPOSIT = 0xBf0a5d6a1f9A4098c69cE660F8b115dc8509b7C9;
    address internal constant ROUTER_MODULE_WITHDRAW = 0xE88772DFB857317476b77F1A25b888b9424Cf63c;
    address internal constant ROUTER_MODULE_CLAIM = 0xFD98cEcB88FC61101D4beBf1b6f9E65572222Ff5;
    address internal constant ROUTER_MODULE_MIGRATION_CURVE = 0x0e5Ca5f4989637d480968325B716Db7A6e46466B;
    address internal constant ROUTER_MODULE_MIGRATION_STAKE_DAO_V1 = 0xf0b84B9334132843fc256830Fb941d535853C120;
    address internal constant ROUTER_MODULE_MIGRATION_YEARN = 0x267C77f0616d44eD6D816527974a624B2Ba65eE3;
}
"
    },
    "src/Factory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ProtocolContext} from "src/ProtocolContext.sol";
import {IRegistrar} from "src/interfaces/IRegistrar.sol";
import {RewardReceiverMigrationModule} from "src/modules/RewardReceiverMigrationModule.sol";

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

/// @notice Factory is an abstract base contract for implementing protocol-specific vault factories.
///         It provides core functionality for creating and managing vaults across different protocols,
///         including deploying vaults and reward receivers for protocol gauges, validating gauges and tokens,
///         registering vaults with the protocol controller, and setting up reward tokens for vaults.
abstract contract Factory is ProtocolContext {
    //////////////////////////////////////////////////////
    // --- IMMUTABLES
    //////////////////////////////////////////////////////

    /// @notice Registrar address which contains deployable gauges for the factory.
    IRegistrar public immutable REGISTRAR;

    /// @notice Reward vault implementation address
    /// @dev The implementation contract that will be cloned for each new vault
    address public immutable REWARD_VAULT_IMPLEMENTATION;

    /// @notice Reward receiver implementation address
    /// @dev The implementation contract that will be cloned for each new reward receiver
    address public immutable REWARD_RECEIVER_IMPLEMENTATION;

    /// @notice Reward migration module address.
    address public immutable REWARD_RECEIVER_MIGRATION_MODULE;

    /// @notice Reward router address.
    address public immutable REWARD_ROUTER;

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

    /// @notice Error thrown when the gauge is not a valid candidate
    error InvalidGauge();

    /// @notice Error thrown when the approve fails
    error ApproveFailed();

    /// @notice Error thrown when the token is not valid
    error InvalidToken();

    /// @notice Error thrown when the deployment is not valid
    error InvalidDeployment();

    /// @notice Error thrown when the gauge has been already used
    error AlreadyDeployed();

    //////////////////////////////////////////////////////
    // --- EVENTS
    //////////////////////////////////////////////////////

    /// @notice Emitted when a new vault is deployed
    /// @param vault Address of the deployed vault
    /// @param asset Address of the underlying asset
    /// @param gauge Address of the associated gauge
    event VaultDeployed(address vault, address asset, address gauge);

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

    /// @notice Initializes the factory with protocol controller, reward token, and vault implementation
    /// @param _protocolController Address of the protocol controller
    /// @param _vaultImplementation Address of the reward vault implementation
    /// @param _rewardReceiverImplementation Address of the reward receiver implementation
    /// @param _protocolId Protocol identifier
    /// @param _locker Address of the locker
    /// @param _gateway Address of the gateway
    constructor(
        address _protocolController,
        address _vaultImplementation,
        address _rewardReceiverImplementation,
        address _rewardReceiverMigrationModule,
        address _rewardRouter,
        bytes4 _protocolId,
        address _locker,
        address _gateway,
        address _registrar
    ) ProtocolContext(_protocolId, _protocolController, _locker, _gateway) {
        require(
            _protocolController != address(0) && _vaultImplementation != address(0)
                && _rewardReceiverImplementation != address(0),
            ZeroAddress()
        );

        REWARD_ROUTER = _rewardRouter;
        REGISTRAR = IRegistrar(_registrar);
        REWARD_VAULT_IMPLEMENTATION = _vaultImplementation;
        REWARD_RECEIVER_IMPLEMENTATION = _rewardReceiverImplementation;
        REWARD_RECEIVER_MIGRATION_MODULE = _rewardReceiverMigrationModule;
    }

    //////////////////////////////////////////////////////
    // --- EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////

    /// @notice Create a new vault for a given gauge
    /// @dev Deploys a vault and reward receiver for the gauge, registers them, and sets up reward tokens
    /// @param gauge Address of the gauge
    /// @return vault Address of the deployed vault
    /// @return rewardReceiver Address of the deployed reward receiver
    /// @custom:throws InvalidGauge If the gauge is not valid
    /// @custom:throws InvalidDeployment If the deployment is not valid
    /// @custom:throws GaugeAlreadyUsed If the gauge has already been used
    function createVault(address gauge) public virtual returns (address vault, address rewardReceiver) {
        /// Perform checks on the gauge to make sure it's valid and can be used
        require(_isValidGauge(gauge), InvalidGauge());
        require(_isValidDeployment(gauge), InvalidDeployment());
        require(PROTOCOL_CONTROLLER.vault(gauge) == address(0), AlreadyDeployed());

        /// Get the asset address from the gauge
        address asset = _getAsset(gauge);

        /// Prepare the initialization data for the vault
        /// The vault needs: gauge and asset
        bytes memory data = abi.encodePacked(gauge, asset);

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

        /// Clone the vault implementation with the initialization data
        vault = Clones.cloneDeterministicWithImmutableArgs(REWARD_VAULT_IMPLEMENTATION, data, salt);

        /// Prepare the initialization data for the reward receiver
        /// The reward receiver needs: vault
        data = abi.encodePacked(vault, address(0), REWARD_ROUTER);

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

        /// Deploy Reward Receiver.
        rewardReceiver = Clones.cloneDeterministicWithImmutableArgs(REWARD_RECEIVER_IMPLEMENTATION, data, salt);

        /// Initialize the vault.
        /// @dev Can be approval if needed etc.
        _initializeVault(vault, asset, gauge);

        /// Register the vault in the protocol controller
        _registerVault(gauge, vault, asset, rewardReceiver);

        /// Add extra reward tokens to the vault
        _setupRewardTokens(vault, gauge, rewardReceiver);

        /// Set the reward receiver for the gauge
        _setRewardReceiver(gauge, rewardReceiver);

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

        emit VaultDeployed(vault, asset, gauge);
    }

    /// @notice Sync reward tokens for a gauge
    /// @dev Updates the reward tokens for an existing vault
    /// @param gauge Address of the gauge
    /// @custom:throws InvalidGauge If the gauge is not valid or has no associated vault
    function syncRewardTokens(address gauge) external {
        address vault = PROTOCOL_CONTROLLER.vault(gauge);
        require(vault != address(0), InvalidGauge());

        /// 0. Migrate the reward receiver if the migration module is set.
        if (REWARD_RECEIVER_MIGRATION_MODULE != address(0)) {
            RewardReceiverMigrationModule(REWARD_RECEIVER_MIGRATION_MODULE).migrate(gauge);
        }

        _setupRewardTokens(vault, gauge, PROTOCOL_CONTROLLER.rewardReceiver(gauge));
    }

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

    /// @notice Get the asset address from a gauge
    /// @dev Must be implemented by derived factories to handle protocol-specific asset retrieval
    /// @param gauge Address of the gauge
    /// @return The address of the asset associated with the gauge
    function _getAsset(address gauge) internal view virtual returns (address);

    /// @notice Check if a deployment is valid
    /// @dev Can be overridden by derived factories to add additional deployment validation
    /// @return True if the deployment is valid, false otherwise
    function _isValidDeployment(address) internal view virtual returns (bool) {
        return true;
    }

    /// @notice Initialize the vault
    /// @param vault Address of the vault
    /// @param asset Address of the asset
    /// @param gauge Address of the gauge
    function _initializeVault(address vault, address asset, address gauge) internal virtual;

    /// @notice Register the vault in the protocol controller
    /// @param gauge Address of the gauge
    /// @param vault Address of the vault
    /// @param asset Address of the asset
    /// @param rewardReceiver Address of the reward receiver
    function _registerVault(address gauge, address vault, address asset, address rewardReceiver) internal {
        PROTOCOL_CONTROLLER.registerVault(gauge, vault, asset, rewardReceiver, PROTOCOL_ID);
    }

    /// @notice Setup reward tokens for the vault
    /// @dev Must be implemented by derived factories to handle protocol-specific reward token setup
    /// @param vault Address of the vault
    /// @param gauge Address of the gauge
    /// @param rewardReceiver Address of the reward receiver
    function _setupRewardTokens(address vault, address gauge, address rewardReceiver) internal virtual;

    /// @notice Set the reward receiver for a gauge
    /// @dev Must be implemented by derived factories to handle protocol-specific reward receiver setup
    /// @param gauge Address of the gauge
    /// @param rewardReceiver Address of the reward receiver
    function _setRewardReceiver(address gauge, address rewardReceiver) internal virtual;

    /// @notice Check if a gauge is valid
    /// @dev Must be implemented by derived factories to handle protocol-specific gauge validation
    /// @param gauge Address of the gauge
    /// @return isValid True if the gauge is valid
    function _isValidGauge(address gauge) internal view virtual returns (bool);

    /// @notice Check if a token is valid as a reward token
    /// @dev Validates that the token is not zero address and not the main reward token
    /// @param token Address of the token
    /// @return isValid True if the token is valid
    function _isValidToken(address token) internal view virtual returns (bool) {
        return token != address(0) && token != REWARD_TOKEN;
    }
}
"
    },
    "src/interfaces/IRewardVault.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IAccountant} from "src/interfaces/IAccountant.sol";

/// @title IRewardVault
/// @notice Interface for the RewardVault contract
interface IRewardVault is IERC4626 {
    function addRewardToken(address rewardsToken, address distributor) external;

    function depositRewards(address _rewardsToken, uint128 _amount) external;

    function deposit(uint256 assets, address receiver, address referrer) external returns (uint256 shares);

    function deposit(address account, address receiver, uint256 assets, address referrer)
        external
        returns (uint256 shares);

    function claim(address[] calldata tokens, address receiver) external returns (uint256[] memory amounts);

    function claim(address account, address[] calldata tokens, address receiver)
        external
        returns (uint256[] memory amounts);

    function getRewardsDistributor(address token) external view returns (address);

    function getLastUpdateTime(address token) external view returns (uint32);

    function getPeriodFinish(address token) external view returns (uint32);

    function getRewardRate(address token) external view returns (uint128);

    function getRewardPerTokenStored(address token) external view returns (uint128);

    function getRewardPerTokenPaid(address token, address account) external view returns (uint128);

    function getClaimable(address token, address account) external view returns (uint128);

    function getRewardTokens() external view returns (address[] memory);

    function lastTimeRewardApplicable(address token) external view returns (uint256);

    function rewardPerToken(address token) external view returns (uint128);

    function earned(address account, address token) external view returns (uint128);

    function isRewardToken(address rewardToken) external view returns (bool);

    function resumeVault() external;

    function gauge() external view returns (address);

    function ACCOUNTANT() external view returns (IAccountant);

    function checkpoint(address account) external;

    function PROTOCOL_ID() external view returns (bytes4);
}
"
    },
    "src/interfaces/ISidecarFactory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

interface ISidecarFactory {
    function sidecar(address gauge) external view returns (address);
    function create(address token, bytes memory args) external returns (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(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "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
            );
    }
}
"
    },
    "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/IRegistrar.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;

/// @title IRegistrar
/// @author Stake DAO
/// @notice Interface for the Registrar contract that manages whitelisted deployment targets per protocol.
interface IRegistrar {
    /// @notice Emitted when a deployment target is whitelisted or unwhitelisted for a protocol.
    /// @param protocolId Protocol ID (bytes4)
    /// @param target     Address (gauge/asset) that is whitelisted or unwhitelisted
    /// @param whitelisted    Boolean if whitelisted
    event WhitelistedTarget(bytes4 indexed protocolId, address indexed target, bool whitelisted);

    /// @notice Set or unset an allowed target for a given protocol.
    /// @param protocolId ID of the protocol (bytes4)
    /// @param target     Address to allow/disallow
    /// @param allowed    Boolean if allowed
    function setAllowed(bytes4 protocolId, address target, bool allowed) external;

    /// @notice Check if a target is whitelisted for a given protocol.
    /// @param protocolId Protocol ID (bytes4)
    /// @param target     Address to check
    /// @return whitelisted   True if whitelisted, false if not whitelisted
    function isWhitelisted(bytes4 protocolId, address target) external view returns (bool whitelisted);
}
"
    },
    "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),
            fa

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Liquidity, Staking, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory|addr:0xef9bef9ab7b578eb0654f0cd2c75519c9a3f7fe1|verified:true|block:23648030|tx:0xa41c911c4b0f0c1290e61887519986998767286c93bc2c9fb75d5e9e60ae937d|first_check:1761331708

Submitted on: 2025-10-24 20:48:29

Comments

Log in to comment.

No comments yet.