HYPE Price: $21.91 (-1.03%)
 

Overview

HYPE Balance

HyperEVM LogoHyperEVM LogoHyperEVM Logo0 HYPE

HYPE Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
CompoundEmissionExtensionUpgradeable

Compiler Version
v0.8.19+commit.7dd6d404

Optimization Enabled:
Yes with 2000 runs

Other Settings:
paris EvmVersion
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.19;

import "./interfaces/ICompoundEmissionExtension.sol";
import "./interfaces/IVoter.sol";
import "./interfaces/IVotingEscrow.sol";
import "../bribes/interfaces/IBribe.sol";

import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";

/**
 * @title CompoundEmissionExtensionUpgradeable
 * @notice
 *  This contract serves as an extension on top of a Voter contract to automatically
 *  compound user emissions into veNFT locks and/or Bribe pools. Users may configure
 *  how their claimed emissions are allocated among:
 *    1) Multiple veNFT locks (via TargetLock[]).
 *    2) Multiple bribe pools (via TargetPool[]).
 *
 *  The user can define what fraction (percentage) of their emissions goes to locks
 *  and what fraction goes to bribe pools. Each fraction’s distribution can further be
 *  split across multiple targets (locks and/or bribe pools).
 *
 *  The contract also supports creating or depositing into veNFT locks with configurable
 *  lock parameters, either via a user-specific config or a global default config.
 *
 * @dev
 *  - Inherits from {ReentrancyGuardUpgradeable} to protect state-mutating functions
 *    from reentrancy attacks.
 *  - Relies on the Voter’s roles to manage who can call certain functions.
 *    Specifically, only addresses with the COMPOUND_KEEPER_ROLE can perform batch
 *    compounding on behalf of users.
 */
contract CompoundEmissionExtensionUpgradeable is ICompoundEmissionExtension, ReentrancyGuardUpgradeable {
    using SafeERC20Upgradeable for IERC20Upgradeable;

    /**
     * @notice Precision factor for percentage calculations (1e18 = 100%).
     */
    uint256 internal constant _PRECISION = 1e18;

    /**
     * @notice Role for the keeper responsible for triggering emission compounding
     *         (e.g., on a regular schedule).
     */
    bytes32 public constant COMPOUND_KEEPER_ROLE = keccak256("COMPOUND_KEEPER_ROLE");

    /**
     * @notice Role for the administrator with permissions to set default create-lock configurations.
     */
    bytes32 public constant COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE = keccak256("COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE");

    /**
     * @notice The address of the Voter contract from which emissions will be claimed.
     */
    address public voter;

    /**
     * @notice The token being locked (and compounded) in the VotingEscrow contract.
     */
    address public token;

    /**
     * @notice The VotingEscrow contract address where emissions are locked.
     */
    address public votingEscrow;

    /**
     * @notice The default configuration for creating new locks if a user has not set a custom config.
     *
     * @dev
     *  - `shouldBoosted` Whether to treat the lock as boosted.
     *  - `withPermanentLock` Whether this lock is permanently locked.
     *  - `lockDuration` The duration (in seconds) for the lock (ignored if `withPermanentLock = true`).
     *  - `managedTokenIdForAttach` An optional existing managed veNFT ID to which this deposit is attached.
     */
    CreateLockConfig public defaultCreateLockConfig;

    /**
     * @notice For each user, the fraction of emissions that should be deposited into veNFT locks (in 1e18 = 100%).
     */
    mapping(address => uint256) public getToLocksPercentage;

    /**
     * @notice For each user, the fraction of emissions that should be deposited into bribe pools (in 1e18 = 100%).
     */
    mapping(address => uint256) public getToBribePoolsPercentage;

    /**
     * @notice Indicates whether a user has a custom `CreateLockConfig` set.
     */
    mapping(address => bool) internal _usersCreateLockConfigIsEnable;

    /**
     * @notice The user’s custom `CreateLockConfig`, if `_usersCreateLockConfigIsEnable[user]` is true.
     */
    mapping(address => CreateLockConfig) internal _usersCreateLockConfigs;

    /**
     * @notice Defines how a user’s allocated portion for veNFT locks is further split among multiple locks.
     *
     * @dev Each entry includes a `tokenId` of an existing veNFT lock and a `percentage` (1e18 = 100%)
     *      indicating how that portion is distributed. All `TargetLock[]` for a user must sum to 1e18
     *      if the user has a nonzero `getToLocksPercentage[user]`.
     */
    mapping(address => TargetLock[]) internal _usersCompoundEmissionTargetLocks;

    /**
     * @notice Defines how a user’s allocated portion for bribe pools is further split among multiple pools.
     *
     * @dev Each entry includes an address of the pool, and a `percentage` (1e18 = 100%)
     *      indicating how that portion is distributed. All `TargetPool[]` for a user must sum to 1e18
     *      if the user has a nonzero `getToBribePoolsPercentage[user]`.
     */
    mapping(address => TargetPool[]) internal _usersCompoundEmissionTargetBribesPools;

    /**
     * @notice Thrown when an invalid lock configuration is provided.
     */
    error InvalidCreateLockConfig();

    /**
     * @notice Thrown when the user sets an invalid emission compounding parameter combination.
     */
    error InvalidCompoundEmissionParams();

    /**
     * @notice Thrown when attempting to set token locks for a veNFT that does not belong to the user.
     */
    error AnotherUserTargetLocks();

    /**
     * @notice Thrown when access is denied for the operation.
     */
    error AccessDenied();

    /**
     * @notice Thrown when attempting to set a target bribe pool that is associated with a killed gauge.
     */
    error TargetPoolGaugeIsKilled();

    /**
     * @dev Restricts execution to addresses holding the specified role in the Voter contract.
     * @param role_ The role required for the function call.
     */
    modifier onlyRole(bytes32 role_) {
        if (!IVoter(voter).hasRole(role_, msg.sender)) {
            revert AccessDenied();
        }
        _;
    }

    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initializes the compound emission extension (called once).
     * @dev
     *  - Sets default lock duration to approximately 6 months (15724800 seconds).
     *  - Should be invoked right after deployment.
     *
     * @param voter_ The address of the Voter contract.
     * @param token_ The address of the token being locked in VotingEscrow.
     * @param votingEscrow_ The address of the VotingEscrow contract.
     */
    function initialize(address voter_, address token_, address votingEscrow_) external initializer {
        __ReentrancyGuard_init();
        voter = voter_;
        token = token_;
        votingEscrow = votingEscrow_;
        defaultCreateLockConfig = CreateLockConfig(false, false, 15724800, 0);
    }

    /**
     * @notice Sets the global default create-lock configuration. This applies to any user that does not have a custom config.
     * @dev Only callable by addresses with the COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE role.
     * @param config_ The new default `CreateLockConfig`.
     *
     * Requirements:
     * - `config_.lockDuration` must be nonzero if `withPermanentLock` is false.
     * - At least one of `withPermanentLock`, `lockDuration`, or `managedTokenIdForAttach` must be set if `shouldBoosted` is false.
     *
     * Emits a {SetDefaultCreateLockConfig} event.
     */
    function setDefaultCreateLockConfig(
        CreateLockConfig calldata config_
    ) external onlyRole(COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE) {
        if (!config_.withPermanentLock && config_.lockDuration == 0 && config_.managedTokenIdForAttach == 0 && !config_.shouldBoosted) {
            revert InvalidCreateLockConfig();
        }
        if (config_.lockDuration == 0 && !config_.withPermanentLock) {
            revert InvalidCreateLockConfig();
        }
        defaultCreateLockConfig = config_;
        emit SetDefaultCreateLockConfig(config_);
    }

    /**
     * @notice Sets or removes a user-specific `CreateLockConfig`.
     * @dev
     *  - If all parameters in `config_` are zero/false, the user's config is removed,
     *    reverting them to using the default config.
     *  - Otherwise, the config must be valid (nonzero lock duration if `withPermanentLock` = false).
     *
     * @param config_ The `CreateLockConfig` for the caller (`msg.sender`).
     *
     * Emits a {SetCreateLockConfig} event.
     */
    function setCreateLockConfig(CreateLockConfig calldata config_) external {
        if (!config_.withPermanentLock && config_.lockDuration == 0 && config_.managedTokenIdForAttach == 0 && !config_.shouldBoosted) {
            delete _usersCreateLockConfigIsEnable[msg.sender];
            delete _usersCreateLockConfigs[msg.sender];
        } else {
            if (config_.lockDuration == 0 && !config_.withPermanentLock) {
                revert InvalidCreateLockConfig();
            }
            _usersCreateLockConfigIsEnable[msg.sender] = true;
            _usersCreateLockConfigs[msg.sender] = config_;
        }
        emit SetCreateLockConfig(msg.sender, config_);
    }

    /**
     * @notice Updates the user’s emission compounding configuration, including:
     *  - Percentages allocated to locks vs. bribe pools.
     *  - The specific lock targets (`TargetLock[]`).
     *  - The specific bribe pool targets (`TargetPool[]`).
     *
     * @dev
     *  - The total of `toLocksPercentage + toBribePoolsPercentage` cannot exceed 1e18 (100%).
     *  - If `toLocksPercentage > 0`, then there must be a nonempty `targetLocks` array (and vice versa).
     *  - If `toBribePoolsPercentage > 0`, then there must be a nonempty `targetsBribePools` array (and vice versa).
     *  - The sum of all `percentage` fields in `targetLocks` must be exactly 1e18 if updating them.
     *  - The sum of all `percentage` fields in `targetsBribePools` must be exactly 1e18 if updating them.
     *  - Each `targetLocks[i].tokenId` must belong to the caller if nonzero.
     *  - Each `targetsBribePools[i].pool` must correspond to a gauge that is alive.
     *
     * @param p_ A struct with the following fields:
     *  - `shouldUpdateGeneralPercentages` Whether to update the overall splits to locks/bribe pools.
     *  - `shouldUpdateTargetLocks`       Whether to replace the entire array of user’s `TargetLock[]`.
     *  - `shouldUpdateTargetBribePools`  Whether to replace the entire array of user’s `TargetPool[]`.
     *  - `toLocksPercentage`             The fraction of user’s emissions allocated to locks (1e18 = 100%).
     *  - `toBribePoolsPercentage`        The fraction of user’s emissions allocated to bribe pools (1e18 = 100%).
     *  - `targetLocks`                   The new `TargetLock[]`, each with a `tokenId` and `percentage`.
     *  - `targetsBribePools`             The new `TargetPool[]`, each with a `pool` and `percentage`.
     *
     * Emits {SetCompoundEmissionGeneralPercentages} if `shouldUpdateGeneralPercentages` is true.
     * Emits {SetCompoundEmissionTargetLocks} if `shouldUpdateTargetLocks` is true.
     * Emits {SetCompoundEmissionTargetBribePools} if `shouldUpdateTargetBribePools` is true.
     *
     * Reverts with {InvalidCompoundEmissionParams} if the inputs fail the above constraints.
     */
    function setCompoundEmissionConfig(UpdateCompoundEmissionConfigParams calldata p_) external {
        uint256 newTargetLocksLength = p_.shouldUpdateTargetLocks
            ? p_.targetLocks.length
            : _usersCompoundEmissionTargetLocks[msg.sender].length;

        uint256 newTargetBribePoolsLength = p_.shouldUpdateTargetBribePools
            ? p_.targetsBribePools.length
            : _usersCompoundEmissionTargetBribesPools[msg.sender].length;

        uint256 newToTargetLocksPercentage = p_.shouldUpdateGeneralPercentages ? p_.toLocksPercentage : getToLocksPercentage[msg.sender];

        uint256 newToTargetBribePoolsPercentage = p_.shouldUpdateGeneralPercentages
            ? p_.toBribePoolsPercentage
            : getToBribePoolsPercentage[msg.sender];

        if (newToTargetLocksPercentage + newToTargetBribePoolsPercentage > _PRECISION) {
            revert InvalidCompoundEmissionParams();
        }

        if (
            (newToTargetLocksPercentage > 0 && newTargetLocksLength == 0) || (newToTargetLocksPercentage == 0 && newTargetLocksLength > 0)
        ) {
            revert InvalidCompoundEmissionParams();
        }

        if (
            (newToTargetBribePoolsPercentage > 0 && newTargetBribePoolsLength == 0) ||
            (newToTargetBribePoolsPercentage == 0 && newTargetBribePoolsLength > 0)
        ) {
            revert InvalidCompoundEmissionParams();
        }

        if (p_.shouldUpdateTargetLocks && newToTargetLocksPercentage > 0) {
            IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
            uint256 targetLocksSumPercentage;
            for (uint256 i; i < p_.targetLocks.length; ) {
                uint256 percentage = p_.targetLocks[i].percentage;
                if (p_.targetLocks[i].tokenId != 0) {
                    if (votingEscrowCache.ownerOf(p_.targetLocks[i].tokenId) != msg.sender) {
                        revert AnotherUserTargetLocks();
                    }
                }

                if (percentage == 0) {
                    revert InvalidCompoundEmissionParams();
                }

                targetLocksSumPercentage += p_.targetLocks[i].percentage;
                unchecked {
                    i++;
                }
            }
            if (targetLocksSumPercentage != _PRECISION) {
                revert InvalidCompoundEmissionParams();
            }

            _usersCompoundEmissionTargetLocks[msg.sender] = p_.targetLocks;
            emit SetCompoundEmissionTargetLocks(msg.sender, p_.targetLocks);
        }

        if (p_.shouldUpdateTargetBribePools && newToTargetBribePoolsPercentage > 0) {
            IVoter voterCache = IVoter(voter);

            uint256 targetBribePoolsSumPercentage;

            for (uint256 i; i < p_.targetsBribePools.length; ) {
                address targetPool = p_.targetsBribePools[i].pool;
                uint256 percentage = p_.targetsBribePools[i].percentage;

                if (targetPool == address(0) || percentage == 0) {
                    revert InvalidCompoundEmissionParams();
                }

                address gauge = voterCache.poolToGauge(targetPool);
                if (!voterCache.isAlive(gauge)) {
                    revert TargetPoolGaugeIsKilled();
                }

                targetBribePoolsSumPercentage += percentage;

                unchecked {
                    i++;
                }
            }

            if (targetBribePoolsSumPercentage != _PRECISION) {
                revert InvalidCompoundEmissionParams();
            }

            _usersCompoundEmissionTargetBribesPools[msg.sender] = p_.targetsBribePools;
            emit SetCompoundEmissionTargetBribePools(msg.sender, p_.targetsBribePools);
        }

        if (p_.shouldUpdateGeneralPercentages) {
            if (newToTargetLocksPercentage == 0) {
                delete _usersCompoundEmissionTargetLocks[msg.sender];
                emit SetCompoundEmissionTargetLocks(msg.sender, p_.targetLocks);
            }
            if (newToTargetBribePoolsPercentage == 0) {
                delete _usersCompoundEmissionTargetBribesPools[msg.sender];
                emit SetCompoundEmissionTargetBribePools(msg.sender, p_.targetsBribePools);
            }

            getToLocksPercentage[msg.sender] = newToTargetLocksPercentage;
            getToBribePoolsPercentage[msg.sender] = newToTargetBribePoolsPercentage;
            emit SetCompoundEmissionGeneralPercentages(msg.sender, p_.toLocksPercentage, p_.toBribePoolsPercentage);
        }
    }

    /**
     * @notice Retrieves the effective `CreateLockConfig` for a user, falling back to `defaultCreateLockConfig` if none is set.
     * @param target_ The address of the user.
     * @return createLockConfig The effective config for the user (custom if set, otherwise default).
     */
    function getUserCreateLockConfig(address target_) public view returns (CreateLockConfig memory createLockConfig) {
        return _usersCreateLockConfigIsEnable[target_] ? _usersCreateLockConfigs[target_] : defaultCreateLockConfig;
    }

    /**
     * @notice Retrieves a user’s overall emission-compounding configuration.
     * @param target_ The address of the user.
     * @return toLocksPercentage              Fraction allocated to veNFT locks (1e18=100%).
     * @return toBribePoolsPercentage         Fraction allocated to bribe pools (1e18=100%).
     * @return isCreateLockCustomConfig       Whether the user has a custom `CreateLockConfig`.
     * @return createLockConfig               The effective `CreateLockConfig` (custom or default).
     * @return targetLocks                    The user’s `TargetLock[]` array.
     * @return targetBribePools               The user’s `TargetPool[]` array.
     */
    function getUserInfo(
        address target_
    )
        external
        view
        returns (
            uint256 toLocksPercentage,
            uint256 toBribePoolsPercentage,
            bool isCreateLockCustomConfig,
            CreateLockConfig memory createLockConfig,
            TargetLock[] memory targetLocks,
            TargetPool[] memory targetBribePools
        )
    {
        toLocksPercentage = getToLocksPercentage[target_];
        toBribePoolsPercentage = getToBribePoolsPercentage[target_];
        createLockConfig = getUserCreateLockConfig(target_);
        targetLocks = _usersCompoundEmissionTargetLocks[target_];
        isCreateLockCustomConfig = _usersCreateLockConfigIsEnable[target_];
        targetBribePools = _usersCompoundEmissionTargetBribesPools[target_];
    }

    /**
     * @notice Batch operation for compounding emissions for multiple users.
     * @dev
     *  - Only callable by addresses with the COMPOUND_KEEPER_ROLE.
     *  - Processes each user’s claim in a single transaction.
     *
     * @param claimsParams_ An array of `ClaimParams` describing each user's claim details:
     *   - `target`: The user whose emissions are being claimed.
     *   - `gauges`: The array of gauge addresses to claim from.
     *   - `blaze`:  Optional data for Blaze-based claims (if applicable).
     */
    function compoundEmissionClaimBatch(ClaimParams[] calldata claimsParams_) external onlyRole(COMPOUND_KEEPER_ROLE) nonReentrant {
        for (uint256 i; i < claimsParams_.length; ) {
            _compoundEmissionClaim(claimsParams_[i]);
            unchecked {
                i++;
            }
        }
    }

    /**
     * @notice Allows a user to directly compound their emissions for the specified gauges.
     * @dev
     *  - Only callable by the user themselves (`claimParams_.target`).
     *
     * @param claimParams_ The `ClaimParams` struct:
     *   - `target`: The user who is claiming.
     *   - `gauges`: The array of gauge addresses to claim from.
     *   - `blaze`:  Optional data for blaze-based claims.
     */
    function compoundEmisisonClaim(ClaimParams calldata claimParams_) external nonReentrant {
        _checkSender(claimParams_.target);
        _compoundEmissionClaim(claimParams_);
    }

    /**
     * @notice Disambiguates changes to a user’s `TargetLock[]` token IDs in case of merges or transfers.
     * @dev
     *  - If multiple entries reference `targetTokenId_`, all will be updated to `newTokenId_`.
     *  - If `newTokenId_ = 0`, these entries are cleared, meaning a new veNFT can be created
     *    next time if that portion is used for compounding.
     *  - Typically called by the Voter after a veNFT merge or transfer event.
     *
     * @param target_        The user whose `TargetLock[]` to update.
     * @param targetTokenId_ The old token ID in the user’s array.
     * @param newTokenId_    The new token ID to replace the old one (0 if removing).
     *
     * Emits a {ChangeEmissionTargetLock} event whenever a replacement occurs.
     */
    function changeEmissionTargetLockId(address target_, uint256 targetTokenId_, uint256 newTokenId_) external nonReentrant {
        _checkSender(voter);
        if (getToLocksPercentage[target_] > 0) {
            TargetLock[] memory targetLocks = _usersCompoundEmissionTargetLocks[target_];
            for (uint256 i; i < targetLocks.length; ) {
                if (targetLocks[i].tokenId == targetTokenId_) {
                    _usersCompoundEmissionTargetLocks[target_][i].tokenId = newTokenId_;
                    emit ChangeEmissionTargetLock(target_, targetTokenId_, newTokenId_);
                }
                unchecked {
                    i++;
                }
            }
        }
    }

    /**
     * @notice Calculates how much of `amountIn_` would be allocated to locks vs. bribe pools for a given user.
     * @dev Does not factor in how that portion is further split among multiple `TargetLock[]` or `TargetPool[]`.
     * @param target_  The user in question.
     * @param amountIn_ The total amount of tokens to be distributed for the user.
     * @return toTargetLocks      The portion allocated to veNFT locks.
     * @return toTargetBribePools The portion allocated to bribe pools.
     */
    function getAmountOutToCompound(
        address target_,
        uint256 amountIn_
    ) external view returns (uint256 toTargetLocks, uint256 toTargetBribePools) {
        toTargetLocks = (getToLocksPercentage[target_] * amountIn_) / _PRECISION;
        toTargetBribePools = (getToBribePoolsPercentage[target_] * amountIn_) / _PRECISION;
    }

    /**
     * @dev Core logic to compound the user’s claimed emissions into veNFT locks
     *      and/or bribe pools, as dictated by their configuration.
     *
     * Steps:
     *   1) Call Voter to claim the user’s emissions from specified gauges.
     *   2) Transfer those claimed tokens from Voter to this contract.
     *   3) Based on user’s config, deposit the appropriate amounts into:
     *       (a) veNFT locks (creating new or depositing into existing).
     *       (b) Bribe pools.
     *
     * @param claimParams_ The user’s claim info from {ClaimParams}.
     */
    function _compoundEmissionClaim(ClaimParams calldata claimParams_) internal {
        IVoter voterCache = IVoter(voter);
        (uint256 toTargetLocks, uint256 toTargetBribePools) = voterCache.onCompoundEmissionClaim(
            claimParams_.target,
            claimParams_.gauges,
            claimParams_.blaze
        );

        if (toTargetLocks + toTargetBribePools == 0) {
            return;
        }

        IERC20Upgradeable tokenCache = IERC20Upgradeable(token);
        IVotingEscrow votingEscrowCache = IVotingEscrow(votingEscrow);
        CreateLockConfig memory userCreateLockConfig = getUserCreateLockConfig(claimParams_.target);

        tokenCache.safeTransferFrom(address(voterCache), address(this), toTargetLocks + toTargetBribePools);

        if (toTargetLocks > 0) {
            tokenCache.safeApprove(address(votingEscrowCache), toTargetLocks);

            TargetLock[] memory targetLocks = _usersCompoundEmissionTargetLocks[claimParams_.target];
            uint256 length = targetLocks.length;

            for (uint256 i; i < length; ) {
                TargetLock memory targetLock = targetLocks[i];
                uint256 amount = (targetLock.percentage * toTargetLocks) / _PRECISION;
                if (targetLock.tokenId == 0) {
                    targetLock.tokenId = votingEscrowCache.createLockFor(
                        amount,
                        userCreateLockConfig.lockDuration,
                        claimParams_.target,
                        userCreateLockConfig.shouldBoosted,
                        userCreateLockConfig.withPermanentLock,
                        userCreateLockConfig.managedTokenIdForAttach
                    );

                    _usersCompoundEmissionTargetLocks[claimParams_.target][i].tokenId = targetLock.tokenId;
                    emit CreateLockFromCompoundEmission(claimParams_.target, targetLock.tokenId, amount);
                } else {
                    votingEscrowCache.depositFor(targetLock.tokenId, amount, false, false);
                    emit CompoundEmissionToTargetLock(claimParams_.target, targetLock.tokenId, amount);
                }
                unchecked {
                    i++;
                }
            }
        }

        if (toTargetBribePools > 0) {
            TargetPool[] memory targetBribePools = _usersCompoundEmissionTargetBribesPools[claimParams_.target];
            uint256 length = targetBribePools.length;

            for (uint256 i; i < length; ) {
                TargetPool memory targetPool = targetBribePools[i];
                address gauge = voterCache.poolToGauge(targetPool.pool);
                uint256 amount = (targetPool.percentage * toTargetBribePools) / _PRECISION;

                if (voterCache.isAlive(gauge)) {
                    address externalBribe = voterCache.getGaugeState(gauge).externalBribe;
                    tokenCache.safeApprove(externalBribe, amount);
                    IBribe(externalBribe).notifyRewardAmount(address(tokenCache), amount);
                    emit CompoundEmissionToBribePool(claimParams_.target, targetPool.pool, amount);
                } else {
                    tokenCache.safeApprove(address(votingEscrowCache), amount);
                    uint256 tokenId = votingEscrowCache.createLockFor(
                        amount,
                        userCreateLockConfig.lockDuration,
                        claimParams_.target,
                        userCreateLockConfig.shouldBoosted,
                        userCreateLockConfig.withPermanentLock,
                        userCreateLockConfig.managedTokenIdForAttach
                    );
                    emit CreateLockFromCompoundEmissionForBribePools(claimParams_.target, targetPool.pool, tokenId, amount);
                }

                unchecked {
                    i++;
                }
            }
        }
    }

    /**
     * @dev Checks that `msg.sender` matches `expected_`; otherwise reverts with {AccessDenied}.
     * @param expected_ The address required for the operation.
     */
    function _checkSender(address expected_) internal view {
        if (msg.sender != expected_) {
            revert AccessDenied();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20PermitUpgradeable {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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 amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` 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 amount) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../extensions/IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20Upgradeable {
    using AddressUpgradeable for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20PermitUpgradeable token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165Upgradeable.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721Upgradeable is IERC165Upgradeable {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IBribe {
    struct Reward {
        uint256 periodFinish;
        uint256 rewardsPerEpoch;
        uint256 lastUpdateTime;
    }
    /* ========== EVENTS ========== */

    event RewardAdded(address indexed rewardToken, uint256 reward, uint256 startTimestamp);
    event Staked(uint256 indexed tokenId, uint256 amount);
    event Withdrawn(uint256 indexed tokenId, uint256 amount);
    event RewardPaid(address indexed user, address indexed rewardsToken, uint256 reward);
    event Recovered(address indexed token, uint256 amount);
    event AddRewardToken(address indexed token);

    function deposit(uint amount, uint tokenId) external;

    function withdraw(uint amount, uint tokenId) external;

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

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

    function getRewardForOwner(uint tokenId, address[] memory tokens) external;

    function getRewardForAddress(address _owner, address[] memory tokens) external;

    function notifyRewardAmount(address token, uint amount) external;

    function addRewardToken(address) external;

    function addRewardTokens(address[] memory) external;

    function initialize(address, address, string memory) external;

    function firstBribeTimestamp() external view returns (uint256);

    function totalSupplyAt(uint256 timestamp) external view returns (uint256);

    function rewardData(address, uint256) external view returns (uint256 periodFinish, uint256 rewardsPerEpoch, uint256 lastUpdateTime);

    function rewardsListLength() external view returns (uint256);

    function getEpochStart() external view returns (uint256);

    function earned(uint256 tokenId, address _rewardToken) external view returns (uint256);

    function earned(address _owner, address _rewardToken) external view returns (uint256);

    function balanceOfAt(uint256 tokenId, uint256 _timestamp) external view returns (uint256);

    function balanceOf(uint256 tokenId) external view returns (uint256);

    function getNextEpochStart() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "./IVoter.sol";

/**
 * @title ICompoundEmissionExtension
 * @notice
 *  This interface defines the external functions and data structures for an extension
 *  that automatically compounds user emissions into veNFT locks and/or bribe pools.
 *  Users can configure how their claimed emissions are split across multiple veNFTs
 *  (`TargetLock[]`) and multiple bribe pools (`TargetPool[]`).
 */
interface ICompoundEmissionExtension {
    /**
     * @notice Describes a specific veNFT token lock target and the fraction of emissions to deposit there.
     * @dev
     *  - `percentage` is in 1e18 format (1e18 = 100%).
     *  - If `tokenId` is zero, a new veNFT lock may be created in the compounding process.
     */
    struct TargetLock {
        /**
         * @notice The identifier of an existing veNFT. If zero, a new veNFT may be created.
         */
        uint256 tokenId;
        /**
         * @notice Fraction of allocated emissions for this target (1e18 = 100%).
         */
        uint256 percentage;
    }

    /**
     * @notice Defines the fraction of emissions to be sent to a particular pool's bribe contract.
     * @dev
     *  - `percentage` is in 1e18 format (1e18 = 100%).
     *  - If the associated gauge is dead/killed, fallback logic may apply (e.g., lock creation).
     */
    struct TargetPool {
        /**
         * @notice The address of the pool whose external bribe contract will receive emissions.
         */
        address pool;
        /**
         * @notice Fraction of allocated emissions for this pool (1e18 = 100%).
         */
        uint256 percentage;
    }

    /**
     * @notice Encapsulates parameters for updating a user's compound-emission configuration in a single call.
     * @dev
     *  - `toLocksPercentage + toBribePoolsPercentage` must not exceed 1e18.
     *  - Sum of percentages in `targetLocks` must be 1e18 if `toLocksPercentage > 0`.
     *  - Sum of percentages in `targetsBribePools` must be 1e18 if `toBribePoolsPercentage > 0`.
     *  - If updating `TargetLock[]` or `TargetPool[]`, the array must match the respective percentage being >0.
     */
    struct UpdateCompoundEmissionConfigParams {
        /**
         * @notice Whether to update the overall fraction sent to locks vs. bribe pools.
         */
        bool shouldUpdateGeneralPercentages;
        /**
         * @notice Whether to replace the user's entire array of `TargetLock[]`.
         */
        bool shouldUpdateTargetLocks;
        /**
         * @notice Whether to replace the user's entire array of `TargetPool[]`.
         */
        bool shouldUpdateTargetBribePools;
        /**
         * @notice Fraction of the user's total emissions allocated to veNFT locks (1e18 = 100%).
         */
        uint256 toLocksPercentage;
        /**
         * @notice Fraction of the user's total emissions allocated to bribe pools (1e18 = 100%).
         */
        uint256 toBribePoolsPercentage;
        /**
         * @notice The new set of veNFT lock targets (replaces the old array if updated).
         */
        TargetLock[] targetLocks;
        /**
         * @notice The new set of bribe pool targets (replaces the old array if updated).
         */
        TargetPool[] targetsBribePools;
    }

    /**
     * @notice Configuration options for creating or depositing into veNFTs during compounding.
     * @dev
     *  - If `withPermanentLock` is `true`, `lockDuration` is ignored (the lock is permanent).
     *  - If `managedTokenIdForAttach` is nonzero, the deposit may be attached to an existing managed veNFT.
     */
    struct CreateLockConfig {
        /**
         * @notice Whether the created lock should be considered "boosted" (if the underlying system supports boosted logic).
         */
        bool shouldBoosted;
        /**
         * @notice Whether the lock is permanent (no withdrawal).
         */
        bool withPermanentLock;
        /**
         * @notice Duration in seconds for the lock if it is not permanent.
         */
        uint256 lockDuration;
        /**
         * @notice An optional managed veNFT ID for attaching a new deposit.
         */
        uint256 managedTokenIdForAttach;
    }

    /**
     * @notice Parameters to claim emissions from the Voter, which are then compounded into locks and/or bribe pools.
     * @dev
     *  - `target` is the user whose emissions are being claimed.
     *  - `gauges` is a list of gauge addresses for which to claim the user’s emissions.
     *  - `blaze` is optional blaze-based claim data (if the Voter supports blaze signature).
     */
    struct ClaimParams {
        /**
         * @notice The user whose emissions will be claimed and compounded.
         */
        address target;
        /**
         * @notice The gauge addresses to claim emissions from.
         */
        address[] gauges;
        /**
         * @notice Optional data for blaze-based claims, if applicable in the Voter implementation.
         */
        IVoter.AggregateClaimBlazeDataParams blaze;
    }

    // --------------------- Events ---------------------

    /**
     * @notice Emitted when the default lock configuration is updated.
     * @param config The new default configuration for creating veNFT locks.
     */
    event SetDefaultCreateLockConfig(CreateLockConfig config);

    /**
     * @notice Emitted when a user sets or removes their custom configuration for creating veNFT locks.
     * @param user   The user whose configuration changed.
     * @param config The new config, or default values if removed.
     */
    event SetCreateLockConfig(address indexed user, CreateLockConfig config);

    /**
     * @notice Emitted when a user updates their overall percentages of emissions allocated to locks vs. bribe pools.
     * @param user                The user whose allocation changed.
     * @param toLocksPercentage   Fraction of emissions allocated to locks (1e18 = 100%).
     * @param toBribePoolsPercentage Fraction of emissions allocated to bribe pools (1e18 = 100%).
     */
    event SetCompoundEmissionGeneralPercentages(address indexed user, uint256 toLocksPercentage, uint256 toBribePoolsPercentage);

    /**
     * @notice Emitted when a user replaces or updates their entire array of lock targets.
     * @param user        The user whose targets changed.
     * @param targetLocks The new array of `TargetLock` structs.
     */
    event SetCompoundEmissionTargetLocks(address indexed user, TargetLock[] targetLocks);

    /**
     * @notice Emitted when a user replaces or updates their entire array of bribe pool targets.
     * @param user              The user whose targets changed.
     * @param targetBribePools  The new array of `TargetPool` structs.
     */
    event SetCompoundEmissionTargetBribePools(address indexed user, TargetPool[] targetBribePools);

    /**
     * @notice Emitted when a user changes the veNFT token ID in an existing emission distribution target lock.
     * @param user             The user making the change.
     * @param targetLockFromId The old token ID to be replaced.
     * @param targetTokenToId  The new token ID (0 if effectively removing the old lock reference).
     */
    event ChangeEmissionTargetLock(address indexed user, uint256 targetLockFromId, uint256 targetTokenToId);

    /**
     * @notice Emitted when a new veNFT lock is created due to emission compounding.
     * @param user    The user for whom the lock was created.
     * @param tokenId The newly created veNFT token ID.
     * @param amount  The amount of tokens locked.
     */
    event CreateLockFromCompoundEmission(address indexed user, uint256 indexed tokenId, uint256 amount);

    /**
     * @notice Emitted when a new veNFT lock is created during fallback logic for bribe pools (e.g., if the gauge is killed).
     * @param user    The user for whom the fallback lock was created.
     * @param pool    The bribe pool that was originally intended to receive tokens.
     * @param tokenId The newly created veNFT token ID.
     * @param amount  The amount of tokens locked instead of bribe distribution.
     */
    event CreateLockFromCompoundEmissionForBribePools(address indexed user, address pool, uint256 indexed tokenId, uint256 amount);

    /**
     * @notice Emitted when a user compounds emissions into a specific bribe pool.
     * @param user   The user compounding emissions.
     * @param pool   The bribe pool receiving the tokens.
     * @param amount The amount of tokens deposited.
     */
    event CompoundEmissionToBribePool(address indexed user, address pool, uint256 amount);

    /**
     * @notice Emitted when a user compounds emissions into an existing veNFT lock.
     * @param user    The address of the user compounding emissions.
     * @param tokenId The identifier of the veNFT receiving the deposit.
     * @param amount  The amount of tokens deposited into the lock.
     */
    event CompoundEmissionToTargetLock(address indexed user, uint256 indexed tokenId, uint256 amount);

    // --------------------- External Functions ---------------------

    /**
     * @notice Batch operation to claim and compound emissions for multiple users simultaneously.
     * @dev
     *  - Only callable by addresses with the COMPOUND_KEEPER_ROLE.
     *  - Iterates over each user's claim, collecting and distributing emissions according to user configs.
     *
     * @param claimsParams_ An array of `ClaimParams`, one for each user to process.
     */
    function compoundEmissionClaimBatch(ClaimParams[] calldata claimsParams_) external;

    /**
     * @notice Allows an individual user to claim and compound emissions for specified gauges.
     * @dev
     *  - Only callable by the user matching `claimParams_.target`.
     *  - Collects emissions from the specified gauges, then distributes them to veNFT locks and bribe pools.
     *
     * @param claimParams_ The struct containing:
     *  - `target`: user whose emissions are being claimed.
     *  - `gauges`: gauges to claim from.
     *  - `merkl`: optional merkle claim data.
     */
    function compoundEmisisonClaim(ClaimParams calldata claimParams_) external;

    /**
     * @notice Updates occurrences of `targetTokenId_` in a user's `TargetLock[]` to `newTokenId_`.
     * @dev
     *  - If multiple entries reference `targetTokenId_`, all will be replaced.
     *  - If `newTokenId_ = 0`, references to `targetTokenId_` are cleared,
     *    effectively enabling a new veNFT to be created in future compounding for that portion.
     *  - Typically called by the Voter after a veNFT transfer or merge.
     *
     * @param target_        The user whose `TargetLock[]` will be updated.
     * @param targetTokenId_ The old token ID to search for in that user's target array.
     * @param newTokenId_    The new token ID to replace the old one. Zero if removing.
     */
    function changeEmissionTargetLockId(address target_, uint256 targetTokenId_, uint256 newTokenId_) external;

    /**
     * @notice Retrieves the fraction (in 1e18 scale) of a user's emissions allocated to veNFT locks.
     * @dev A value of 1e18 indicates 100%. If this returns 5e17, that means 50% is allocated.
     * @param target_ The user address to query.
     * @return The fraction of emissions (1e18 = 100%) allocated to locks.
     */
    function getToLocksPercentage(address target_) external view returns (uint256);

    /**
     * @notice Returns how much of a given `amountIn_` would be allocated to locks vs. bribe pools for a user.
     * @dev Does not break down per veNFT or per pool, only the high-level split.
     * @param target_   The user whose configuration to apply.
     * @param amountIn_ The total emission amount to be distributed.
     * @return toTargetLocks      The portion allocated to veNFT locks.
     * @return toTargetBribePools The portion allocated to bribe pools.
     */
    function getAmountOutToCompound(
        address target_,
        uint256 amountIn_
    ) external view returns (uint256 toTargetLocks, uint256 toTargetBribePools);
}

File 13 of 14 : IVoter.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IAccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol";

interface IVoter is IAccessControlUpgradeable {
    /**
     * @notice Represents the state of a gauge.
     * @param isGauge Indicates if the address is a gauge.
     * @param isAlive Indicates if the gauge is active.
     * @param internalBribe The address of the internal bribe contract.
     * @param externalBribe The address of the external bribe contract.
     * @param pool The address of the associated pool.
     * @param claimable The amount of rewards claimable by the gauge.
     * @param index The current index used for reward distribution calculations.
     * @param lastDistributionTimestamp The last time rewards were distributed.
     */
    struct GaugeState {
        bool isGauge;
        bool isAlive;
        address internalBribe;
        address externalBribe;
        address pool;
        uint256 claimable;
        uint256 index;
        uint256 lastDistributionTimestamp;
    }

    /**
     * @notice Parameters for creating a veNFT through VotingEscrow.
     * @param percentageToLock The percentage (in 18 decimals) of the claimed reward tokens to be locked.
     * @param lockDuration The duration (in seconds) for which the tokens will be locked.
     * @param to The address that will receive the veNFT.
     * @param shouldBoosted Indicates whether the veNFT should have boosted properties.
     * @param withPermanentLock Indicates if the lock should be permanent.
     * @param managedTokenIdForAttach The ID of the managed veNFT token to which this lock will be attached.
     */
    struct AggregateCreateLockParams {
        uint256 percentageToLock;
        uint256 lockDuration;
        address to;
        bool shouldBoosted;
        bool withPermanentLock;
        uint256 managedTokenIdForAttach;
    }

    /**
     * @notice Parameters for claiming bribes using a specific tokenId.
     * @param tokenId The token ID to claim bribes for.
     * @param bribes The array of bribe contract addresses.
     * @param tokens The array of arrays containing token addresses for each bribe.
     */
    struct AggregateClaimBribesByTokenIdParams {
        uint256 tokenId;
        address[] bribes;
        address[][] tokens;
    }

    /**
     * @notice Parameters for claiming bribes.
     * @param bribes The array of bribe contract addresses.
     * @param tokens The array of arrays containing token addresses for each bribe.
     */
    struct AggregateClaimBribesParams {
        address[] bribes;
        address[][] tokens;
    }

    /**
     * @notice Parameters for claiming Blaze data.
     * @param totalAmount The total amount of reward being claimed.
     * @param deadline The expiration time of the claim.
     * @param signature The signature authorizing the claim.
     */
    struct AggregateClaimBlazeDataParams {
        uint256 totalAmount;
        uint256 deadline;
        bytes signature;
    }

    /**
     * @notice Parameters for claiming VeNest Merkl airdrop data.
     * @param inPureTokens_ Boolean indicating if the claim is in pure tokens.
     * @param amount The amount to claim.
     * @param withPermanentLock_ Whether the lock should be permanent.
     * @param managedTokenIdForAttach_ The ID of the managed NFT to attach, if any. 0 for ignore
     * @param proofs The array of Merkle proofs.
     */
    struct AggregateClaimVeNestMerklAirdrop {
        bool inPureTokens;
        uint256 amount;
        bool withPermanentLock;
        uint256 managedTokenIdForAttach;
        bytes32[] proofs;
    }

    /**
     * @notice Emitted when a gauge is created.
     * @param gauge The address of the created gauge.
     * @param creator The address of the creator.
     * @param internalBribe The address of the created internal bribe.
     * @param externalBribe The address of the created external bribe.
     * @param pool The address of the associated pool.
     */
    event GaugeCreated(address indexed gauge, address creator, address internalBribe, address indexed externalBribe, address indexed pool);

    /**
     * @notice Emitted when a gauge is created.
     * @param gauge The address of the created gauge.
     * @param gaugeType Type identifier of the created gauge.
     */
    event GaugeCreatedType(address indexed gauge, uint256 indexed gaugeType);

    /**
     * @notice Emitted when a gauge is killed.
     * @param gauge The address of the killed gauge.
     */
    event GaugeKilled(address indexed gauge);

    /**
     * @notice Emitted when a gauge is revived.
     * @param gauge The address of the revived gauge.
     */
    event GaugeRevived(address indexed gauge);

    /**
     * @dev Emitted when a user casts votes for multiple pools using a specific token.
     *
     * @param voter The address of the user who cast the votes.
     * @param tokenId The ID of the token used for voting.
     * @param epoch The epoch during which the votes were cast.
     * @param pools An array of pool addresses that received votes.
     * @param voteWeights An array representing the weight of votes allocated to each pool.
     *
     * Requirements:
     * - `pools` and `voteWeights` arrays must have the same length.
     *
     * Note: The voting power represented in `voteWeights` is allocated across the specified `votedPools` for the given `epoch`.
     *       The `totalVotingPower` represents the cumulative voting power used in this vote.
     */
    event VoteCast(
        address indexed voter,
        uint256 indexed tokenId,
        uint256 indexed epoch,
        address[] pools,
        uint256[] voteWeights,
        uint256 totalVotingPower
    );

    /**
     * @dev Emitted when a user resets all votes for the current epoch.
     *
     * @param voter The address of the user who resets their votes.
     * @param tokenId The ID of the token used for voting that is being reset.
     * @param epoch The epoch during which the votes were reset.
     * @param totalResetVotingPower The total voting power that was reset.
     *
     * Note: This event indicates that all previously cast votes for the specified `epoch` have been reset for the given `votingTokenId`.
     *       The `totalResetVotingPower` represents the cumulative voting power that was removed during the reset.
     */
    event VoteReset(address indexed voter, uint256 indexed tokenId, uint256 indexed epoch, uint256 totalResetVotingPower);

    /**
     * @notice Emitted when rewards are notified for distribution.
     * @param sender The address of the sender.
     * @param reward The address of the reward token.
     * @param amount The amount of rewards to distribute.
     */
    event NotifyReward(address indexed sender, address indexed reward, uint256 amount);

    /**
     * @notice Emitted when rewards are distributed to a gauge.
     * @param sender The address of the sender.
     * @param gauge The address of the gauge receiving the rewards.
     * @param amount The amount of rewards distributed.
     */
    event DistributeReward(address indexed sender, address indexed gauge, uint256 amount);

    /**
     * @notice Emitted when the vote delay is updated.
     * @param old The previous vote delay.
     * @param latest The new vote delay.
     */
    event SetVoteDelay(uint256 old, uint256 latest);

    /**
     * @notice Emitted when a contract address is updated.
     * @param key The key representing the contract.
     * @param value The new address of the contract.
     */
    event UpdateAddress(string key, address indexed value);

    /// @notice Event emitted when voting is paused or unpaused.
    /// @dev Emits the current paused state of voting.
    /// @param paused Indicates whether voting is paused (true) or unpaused (false).
    event VotingPaused(bool indexed paused);

    /**
     * @notice Emitted when the distribution window duration is set or updated.
     * @param duration New duration of the distribution window in seconds.
     */
    event SetDistributionWindowDuration(uint256 indexed duration);

    /**
     * @notice Emitted when a token is attached to a managed NFT.
     * @param tokenId ID of the user's token that is being attached.
     * @param managedTokenId ID of the managed token to which the user's token is attached.
     */
    event AttachToManagedNFT(uint256 indexed tokenId, uint256 indexed managedTokenId);

    /**
     * @notice Emitted when a token is detached from a managed NFT.
     * @param tokenId ID of the user's token that is being detached.
     */
    event DettachFromManagedNFT(uint256 indexed tokenId);

    /**
     * @notice Updates the address of a specified contract.
     * @param key_ The key representing the contract.
     * @param value_ The new address of the contract.
     */
    function updateAddress(string memory key_, address value_) external;

    /**
     * @notice Sets the duration of the distribution window for voting.
     * @param distributionWindowDuration_ The duration in seconds.
     */
    function setDistributionWindowDuration(uint256 distributionWindowDuration_) external;

    /**
     * @notice Disables a gauge, preventing further rewards distribution.
     * @param gauge_ The address of the gauge to be disabled.
     */
    function killGauge(address gauge_) external;

    /**
     * @notice Revives a previously disabled gauge, allowing it to distribute rewards again.
     * @param gauge_ The address of the gauge to be revived.
     */
    function reviveGauge(address gauge_) external;

    /**
     * @notice Creates a new V2 gauge for a specified pool.
     * @param pool_ The address of the pool for which to create a gauge.
     * @return gauge The address of the created gauge.
     * @return internalBribe The address of the created internal bribe.
     * @return externalBribe The address of the created external bribe.
     */
    function createV2Gauge(address pool_) external returns (address gauge, address internalBribe, address externalBribe);

    /**
     * @notice Creates a new V3 gauge for a specified pool.
     * @param pool_ The address of the pool for which to create a gauge.
     * @return gauge The address of the created gauge.
     * @return internalBribe The address of the created internal bribe.
     * @return externalBribe The address of the created external bribe.
     */
    function createV3Gauge(address pool_) external returns (address gauge, address internalBribe, address externalBribe);

    /**
     * @notice Creates a custom gauge with specified parameters.
     * @param gauge_ The address of the custom gauge.
     * @param pool_ The address of the pool for which to create a gauge.
     * @param tokenA_ The address of token A in the pool.
     * @param tokenB_ The address of token B in the pool.
     * @param externalBribesName_ The name of the external bribe.
     * @param internalBribesName_ The name of the internal bribe.
     * @return gauge The address of the created gauge.
     * @return internalBribe The address of the created internal bribe.
     * @return externalBribe The address of the created external bribe.
     */
    function createCustomGauge(
        address gauge_,
        address pool_,
        address tokenA_,
        address tokenB_,
        string memory externalBribesName_,
        string memory internalBribesName_
    ) external returns (address gauge, address internalBribe, address externalBribe);

    /**
     * @notice Notifies the contract of a reward amount to be distributed.
     * @param amount_ The amount of rewards to distribute.
     */
    function notifyRewardAmount(uint256 amount_) external;

    /**
     * @notice Distributes fees to a list of gauges.
     * @param gauges_ An array of gauge addresses to distribute fees to.
     */
    function distributeFees(address[] calldata gauges_) external;

    /**
     * @notice Distributes rewards to all pools managed by the contract.
     */
    function distributeAll() external;

    /**
     * @notice Distributes rewards to a specified range of pools.
     * @param start_ The starting index of the pool array.
     * @param finish_ The ending index of the pool array.
     */
    function distribute(uint256 start_, uint256 finish_) external;

    /**
     * @notice Distributes rewards to a specified list of gauges.
     * @param gauges_ An array of gauge addresses to distribute rewards to.
     */
    function distribute(address[] calldata gauges_) external;

    /**
     * @notice Resets the votes for a given NFT token ID.
     * @param tokenId_ The token ID for which to reset votes.
     */
    function reset(uint256 tokenId_) external;

    /**
     * @notice Updates the voting preferences for a given token ID.
     * @param tokenId_ The token ID for which to update voting preferences.
     */
    function poke(uint256 tokenId_) external;

    /**
     * @notice Casts votes for a given NFT token ID.
     * @param tokenId_ The token ID for which to cast votes.
     * @param poolsVotes_ An array of pool addresses to vote for.
     * @param weights_ An array of weights corresponding to the pools.
     */
    function vote(uint256 tokenId_, address[] calldata poolsVotes_, uint256[] calldata weights_) external;

    /**
     * @notice Claims rewards from multiple gauges.
     * @param _gauges An array of gauge addresses to claim rewards from.
     */
    function claimRewards(address[] memory _gauges) external;

    /**
     * @notice Claims bribes for a given NFT token ID from multiple bribe contracts.
     * @param _bribes An array of bribe contract addresses to claim bribes from.
     * @param _tokens An array of token arrays, specifying the tokens to claim.
     * @param tokenId_ The token ID for which to claim bribes.
     */
    function claimBribes(address[] memory _bribes, address[][] memory _tokens, uint256 tokenId_) external;

    /**
     * @notice Claims bribes from multiple bribe contracts.
     * @param _bribes An array of bribe contract addresses to claim bribes from.
     * @param _tokens An array of token arrays, specifying the tokens to claim.
     */
    function claimBribes(address[] memory _bribes, address[][] memory _tokens) external;

    /**
     * @notice Handles the deposit of voting power to a managed NFT.
     * @dev This function is called after tokens are deposited into the Voting Escrow contract for a managed NFT.
     *      Only callable by the Voting Escrow contract.
     * @param tokenId_ The ID of the token that has received the deposit.
     * @param managedTokenId_ The ID of the managed token receiving the voting power.
     * @custom:error AccessDenied Thrown if the caller is not the Voting Escrow contract.
     */
    function onDepositToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external;

    /**
     * @notice Attaches a tokenId to a managed tokenId.
     * @param tokenId_ The user's tokenId to be attached.
     * @param managedTokenId_ The managed tokenId to attach to.
     */
    function attachToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external;

    /**
     * @notice Detaches a tokenId from its managed tokenId.
     * @param tokenId_ The user's tokenId to be detached.
     */
    function dettachFromManagedNFT(uint256 tokenId_) external;

    /**
     * @notice Checks if the provided address is a registered gauge.
     * @param gauge_ The address of the gauge to check.
     * @return True if the address is a registered gauge, false otherwise.
     */
    function isGauge(address gauge_) external view returns (bool);

    /**
     * @notice Returns the state of a specific gauge.
     * @param gauge_ The address of the gauge.
     * @return GaugeState The current state of the specified gauge.
     */
    function getGaugeState(address gauge_) external view returns (GaugeState memory);

    /**
     * @notice Checks if the specified gauge is alive (i.e., enabled for reward distribution).
     * @param gauge_ The address of the gauge to check.
     * @return True if the gauge is alive, false otherwise.
     */
    function isAlive(address gauge_) external view returns (bool);

    /**
     * @notice Returns the pool address associated with a specified gauge.
     * @param gauge_ The address of the gauge to query.
     * @return The address of the pool associated with the specified gauge.
     */
    function poolForGauge(address gauge_) external view returns (address);

    /**
     * @notice Returns the gauge address associated with a specified pool.
     * @param pool_ The address of the pool to query.
     * @return The address of the gauge associated with the specified pool.
     */
    function poolToGauge(address pool_) external view returns (address);

    /**
     * @notice Returns the address of the Voting Escrow contract.
     * @return The address of the Voting Escrow contract.
     */
    function votingEscrow() external view returns (address);

    /**
     * @notice Returns the address of the Minter contract.
     * @return The address of the Minter contract.
     */
    function minter() external view returns (address);

    /**
     * @notice Returns the address of the V2 Pool Factory contract.
     * @return The address of the V2 Pool Factory contract.
     */
    function v2PoolFactory() external view returns (address);

    /**
     * @notice Returns the address of the V3 Pool Factory contract.
     * @return The address of the V3 Pool Factory contract.
     */
    function v3PoolFactory() external view returns (address);

    /**
     * @notice Returns the V2 pool address at a specific index.
     * @param index The index of the V2 pool.
     * @return The address of the V2 pool at the specified index.
     */
    function v2Pools(uint256 index) external view returns (address);

    /**
     * @notice Returns the V3 pool address at a specific index.
     * @param index The index of the V3 pool.
     * @return The address of the V3 pool at the specified index.
     */
    function v3Pools(uint256 index) external view returns (address);

    /**
     * @notice Returns the total number of pools, V2 pools, and V3 pools managed by the contract.
     * @return totalCount The total number of pools.
     * @return v2PoolsCount The total number of V2 pools.
     * @return v3PoolsCount The total number of V3 pools.
     */
    function poolsCounts() external view returns (uint256 totalCount, uint256 v2PoolsCount, uint256 v3PoolsCount);

    /**
     * @notice Returns the current epoch timestamp used for reward calculations.
     * @return The current epoch timestamp.
     */
    function epochTimestamp() external view returns (uint256);

    /**
     * @notice Returns the weight for a specific pool in a given epoch.
     * @param timestamp The timestamp of the epoch.
     * @param pool The address of the pool.
     * @return The weight of the pool in the specified epoch.
     */
    function weightsPerEpoch(uint256 timestamp, address pool) external view returns (uint256);

    /**
     * @notice Returns the vote weight of a specific NFT token ID for a given pool.
     * @param tokenId The ID of the NFT.
     * @param pool The address of the pool.
     * @return The vote weight of the token for the specified pool.
     */
    function votes(uint256 tokenId, address pool) external view returns (uint256);

    /**
     * @notice Returns the number of pools that an NFT token ID has voted for.
     * @param tokenId The ID of the NFT.
     * @return The number of pools the token has voted for.
     */
    function poolVoteLength(uint256 tokenId) external view returns (uint256);

    /**
     * @notice Returns the pool address at a specific index for which the NFT token ID has voted.
     * @param tokenId The ID of the NFT.
     * @param index The index of the pool.
     * @return The address of the pool at the specified index.
     */
    function poolVote(uint256 tokenId, uint256 index) external view returns (address);

    /**
     * @notice Returns the last timestamp when an NFT token ID voted.
     * @param tokenId The ID of the NFT.
     * @return The timestamp of the last vote.
     */
    function lastVotedTimestamps(uint256 tokenId) external view returns (uint256);

    /**
     * @notice Called after a token transfer to update external logic or linkage.
     * @dev Typically invoked by the VotingEscrow contract whenever a veNFT changes ownership.
     *      Implementations can handle scenario-specific logic such as emission extension or target lock updates.
     * @param from_ The address from which the token is transferred.
     * @param to_ The address to which the token is transferred.
     * @param tokenId_ The ID of the token being transferred.
     */
    function onAfterTokenTransfer(address from_, address to_, uint256 tokenId_) external;

    /**
     * @notice Called after two veNFT tokens are merged into one.
     * @dev Typically invoked by the VotingEscrow contract during the merge operation.
     *      Implementations can adjust bookkeeping, reward balances, or other logic related to the merged tokens.
     * @param fromTokenId_ The ID of the token that is merged (source).
     * @param toTokenId_ The ID of the token that remains (destination).
     */
    function onAfterTokenMerge(uint256 fromTokenId_, uint256 toTokenId_) external;

    /**
     * @notice This function is called by the CompoundEmissionExtension to process a user’s reward claims
     *  and determine how much of the claimed tokens will be routed into veNFT locks and/or bribe pools.
     *
     * @param target_ The address of the user for whom the emission claim is being processed.
     * @param gauges_ The array of gauge addresses from which to claim rewards on behalf of `target_`.
     * @param blaze_  Optional Blaze-based claim data (if the Voter supports Blaze claims).
     *
     * @return toTargetLocks      The portion of claimed tokens that should go into veNFT locks.
     * @return toTargetBribePools The portion of claimed tokens that should go into bribe pools.
     */
    function onCompoundEmissionClaim(
        address target_,
        address[] calldata gauges_,
        AggregateClaimBlazeDataParams calldata blaze_
    ) external returns (uint256 toTargetLocks, uint256 toTargetBribePools);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";

/**
 * @title IVotingEscrow
 * @notice Interface for Voting Escrow, allowing users to lock tokens in exchange for veNFTs that are used in governance and other systems.
 */
interface IVotingEscrow is IERC721Upgradeable {
    /**
     * @notice Enum representing the types of deposits that can be made.
     * @dev Defines the context in which a deposit is made:
     * - `DEPOSIT_FOR_TYPE`: Regular deposit for an existing lock.
     * - `CREATE_LOCK_TYPE`: Creating a new lock.
     * - `INCREASE_UNLOCK_TIME`: Increasing the unlock time for an existing lock.
     * - `MERGE_TYPE`: Merging two locks together.
     */
    enum DepositType {
        DEPOSIT_FOR_TYPE,
        CREATE_LOCK_TYPE,
        INCREASE_UNLOCK_TIME,
        MERGE_TYPE
    }

    /**
     * @notice Structure representing the state of a token.
     * @dev This includes information about the lock, voting status, attachment status,
     *      the block of the last transfer, and the index (epoch) of its latest checkpoint.
     * @param locked The locked balance (amount + end timestamp + permanent status) of the token.
     * @param isVoted Whether the token has been used to vote in the current epoch.
     * @param isAttached Whether the token is attached to a managed NFT.
     * @param lastTranferBlock The block number of the last transfer.
     * @param pointEpoch The epoch (checkpoint index) for the token’s most recent voting power change.
     */
    struct TokenState {
        LockedBalance locked;
        bool isVoted;
        bool isAttached;
        uint256 lastTranferBlock;
        uint256 pointEpoch;
    }

    /**
     * @notice Structure representing a locked balance.
     * @dev Contains the amount locked, the end timestamp of the lock, and whether the lock is permanent.
     * @param amount The amount of tokens locked (signed integer for slope calculations).
     * @param end The timestamp when the lock ends (0 if permanently locked).
     * @param isPermanentLocked Whether the lock is permanent (no unlock time).
     */
    struct LockedBalance {
        int128 amount;
        uint256 end;
        bool isPermanentLocked;
    }

    /**
     * @notice Structure representing a point in time for calculating voting power.
     * @dev Used for slope/bias math across epochs.
     * @param bias The bias of the lock, representing the remaining voting power.
     * @param slope The rate at which voting power (bias) decays over time.
     * @param ts The timestamp of the checkpoint.
     * @param blk The block number of the checkpoint.
     * @param permanent The permanently locked amount at this checkpoint.
     */
    struct Point {
        int128 bias;
        int128 slope; // -dweight / dt
        uint256 ts;
        uint256 blk; // block
        int128 permanent;
    }

    /**
     * @notice Emitted when a boost is applied to a token's lock.
     * @param tokenId The ID of the token that received the boost.
     * @param value The amount of tokens used as a boost.
     */
    event Boost(uint256 indexed tokenId, uint256 value);

    /**
     * @notice Emitted when a deposit is made into a lock.
     * @param provider The address of the entity making the deposit.
     * @param tokenId The ID of the token associated with the deposit.
     * @param value The amount of tokens deposited.
     * @param locktime The time (timestamp) until which the lock is extended.
     * @param deposit_type The type of deposit (see {DepositType}).
     * @param ts The timestamp when the deposit was made.
     */
    event Deposit(address indexed provider, uint256 tokenId, uint256 value, uint256 indexed locktime, DepositType deposit_type, uint256 ts);

    /**
     * @notice Emitted when tokens are deposited to an attached NFT.
     * @param provider The address of the user making the deposit.
     * @param tokenId The ID of the NFT receiving the deposit.
     * @param managedTokenId The ID of the managed token receiving the voting power.
     * @param value The amount of tokens deposited.
     */
    event DepositToAttachedNFT(address indexed provider, uint256 tokenId, uint256 managedTokenId, uint256 value);

    /**
     * @notice Emitted when a withdrawal is made from a lock.
     * @param provider The address of the entity making the withdrawal.
     * @param tokenId The ID of the token associated with the withdrawal.
     * @param value The amount of tokens withdrawn.
     * @param ts The timestamp when the withdrawal occurred.
     */
    event Withdraw(address indexed provider, uint256 tokenId, uint256 value, uint256 ts);

    /**
     * @notice Emitted when the merging process of two veNFT locks is initiated.
     * @param tokenFromId The ID of the token being merged from.
     * @param tokenToId The ID of the token being merged into.
     */
    event MergeInit(uint256 tokenFromId, uint256 tokenToId);

    /**
     * @notice Emitted when two veNFT locks are successfully merged.
     * @param provider The address of the entity initiating the merge.
     * @param tokenIdFrom The ID of the token being merged from.
     * @param tokenIdTo The ID of the token being merged into.
     */
    event Merge(address indexed provider, uint256 tokenIdFrom, uint256 tokenIdTo);

    /**
     * @notice Emitted when the total supply of voting power changes.
     * @param prevSupply The previous total supply of voting power.
     * @param supply The new total supply of voting power.
     */
    event Supply(uint256 prevSupply, uint256 supply);

    /**
     * @notice Emitted when an address associated with the contract is updated.
     * @param key The key representing the contract being updated.
     * @param value The new address of the contract.
     */
    event UpdateAddress(string key, address indexed value);

    /**
     * @notice Emitted when a token is permanently locked by a user.
     * @param sender The address of the user who initiated the lock.
     * @param tokenId The ID of the token that has been permanently locked.
     */
    event LockPermanent(address indexed sender, uint256 indexed tokenId);

    /**
     * @notice Emitted when a token is unlocked from a permanent lock state by a user.
     * @param sender The address of the user who initiated the unlock.
     * @param tokenId The ID of the token that has been unlocked from its permanent state.
     */
    event UnlockPermanent(address indexed sender, uint256 indexed tokenId);

    /**
     * @notice Emitted when a veNEST NFT lock is burned and the underlying NEST is released for use in bribes.
     * @param sender The address which initiated the burn-to-bribes operation.
     * @param tokenId The identifier of the veNEST NFT that was burned.
     * @param value The amount of NEST tokens released from the burned lock.
     */
    event BurnToBribes(address indexed sender, uint256 indexed tokenId, uint256 value);

    /**
     * @notice Returns the address of the token used in voting escrow.
     * @return The address of the token contract.
     */
    function token() external view returns (address);

    /**
     * @notice Returns the address of the voter contract.
     * @return The address of the voter.
     */
    function voter() external view returns (address);

    /**
     * @notice Checks if the specified address is approved or the owner of the given token.
     * @param sender The address to check.
     * @param tokenId The ID of the token to check.
     * @return True if `sender` is approved or the owner of `tokenId`, otherwise false.
     */
    function isApprovedOrOwner(address sender, uint256 tokenId) external view returns (bool);

    /**
     * @notice Checks if a specific NFT token is transferable.
     * @dev In the current implementation, this function always returns `true`,
     *      meaning the contract does not enforce non-transferability at code level.
     * @param tokenId_ The ID of the NFT to check.
     * @return bool Always returns true in the current version.
     */
    function isTransferable(uint256 tokenId_) external view returns (bool);

    /**
     * @notice Retrieves the state of a specific NFT.
     * @param tokenId_ The ID of the NFT to query.
     * @return The current {TokenState} of the specified NFT.
     */
    function getNftState(uint256 tokenId_) external view returns (TokenState memory);

    /**
     * @notice Returns the total supply of voting power at the current block timestamp.
     * @return The total supply of voting power.
     */
    function votingPowerTotalSupply() external view returns (uint256);

    /**
     * @notice Returns the balance of a veNFT at the current block timestamp.
     * @dev Balance is determined by the lock’s slope and bias at this moment.
     * @param tokenId_ The ID of the veNFT to query.
     * @return The current voting power (balance) of the veNFT.
     */
    function balanceOfNFT(uint256 tokenId_) external view returns (uint256);

    /**
     * @notice Returns the balance of a veNFT at the current block timestamp, ignoring ownership changes.
     * @dev This function is similar to {balanceOfNFT} but does not zero out the balance
     *      if the token was transferred in the same block.
     * @param tokenId_ The ID of the veNFT to query.
     * @return The current voting power (balance) of the veNFT.
     */
    function balanceOfNftIgnoreOwnershipChange(uint256 tokenId_) external view returns (uint256);

    /**
     * @notice Updates the address of a specified contract.
     * @param key_ The key representing the contract.
     * @param value_ The new address of the contract.
     * @dev Reverts with `InvalidAddressKey` if the key does not match any known setting.
     * Emits an {UpdateAddress} event on success.
     */
    function updateAddress(string memory key_, address value_) external;

    /**
     * @notice Hooks the voting state for a specified NFT.
     * @dev Only callable by the voter contract. Used to mark a veNFT as having voted or not.
     * @param tokenId_ The ID of the NFT.
     * @param state_ True if the NFT is now considered “voted,” false otherwise.
     * @custom:error AccessDenied If called by any address other than the voter.
     */
    function votingHook(uint256 tokenId_, bool state_) external;

    /**
     * @notice Creates a new lock for a specified recipient.
     * @param amount_ The amount of tokens to lock.
     * @param lockDuration_ The duration in seconds for which the tokens will be locked.
     * @param to_ The address of the recipient who will receive the new veNFT.
     * @param shouldBoosted_ Whether the deposit should attempt to get a veBoost.
     * @param withPermanentLock_ Whether the lock should be created as a permanent lock.
     * @param managedTokenIdForAttach_ (Optional) The ID of the managed NFT to attach. Pass 0 to ignore.
     * @return The ID of the newly created veNFT.
     * @dev Reverts with `InvalidLockDuration` if lockDuration_ is 0 or too large.
     *      Reverts with `ValueZero` if amount_ is 0.
     * Emits a {Deposit} event on success.
     */
    function createLockFor(
        uint256 amount_,
        uint256 lockDuration_,
        address to_,
        bool shouldBoosted_,
        bool withPermanentLock_,
        uint256 managedTokenIdForAttach_
    ) external returns (uint256);

    /**
     * @notice Deposits tokens for a specific NFT, increasing its locked balance.
     * @param tokenId_ The ID of the veNFT to top up.
     * @param amount_ The amount of tokens to deposit.
     * @param shouldBoosted_ Whether this deposit should attempt to get a veBoost.
     * @param withPermanentLock_ Whether to apply a permanent lock alongside the deposit.
     * @dev Reverts with `ValueZero` if amount_ is 0.
     * Emits a {Deposit} event upon success.
     */
    function depositFor(uint256 tokenId_, uint256 amount_, bool shouldBoosted_, bool withPermanentLock_) external;

    /**
     * @notice Increases the unlock time for an existing lock.
     * @param tokenId_ The ID of the veNFT to extend.
     * @param lockDuration_ The additional duration in seconds to add to the current unlock time.
     * @dev Reverts with `InvalidLockDuration` if the new unlock time is invalid.
     *      Reverts with `AccessDenied` if the caller is not the owner or approved.
     * Emits a {Deposit} event with the deposit type set to {INCREASE_UNLOCK_TIME}.
     */
    function increase_unlock_time(uint256 tokenId_, uint256 lockDuration_) external;

    /**
     * @notice Deposits tokens and extends the lock duration for a veNFT in one call.
     * @dev This may trigger veBoost if conditions are met.
     * @param tokenId_ The ID of the veNFT.
     * @param amount_ The amount of tokens to deposit.
     * @param lockDuration_ The duration in seconds to add to the current unlock time.
     * Emits one {Deposit} event for the deposit itself
     * and another {Deposit} event for the unlock time increase.
     */
    function depositWithIncreaseUnlockTime(uint256 tokenId_, uint256 amount_, uint256 lockDuration_) external;

    /**
     * @notice Deposits tokens directly into a veNFT that is attached to a managed NFT.
     * @dev This updates the locked balance on the managed NFT, adjusts total supply,
     *      and emits {DepositToAttachedNFT} and {Supply} events.
     * @param tokenId_ The ID of the attached veNFT.
     * @param amount_ The amount of tokens to deposit.
     * @custom:error NotManagedNft if the managed token ID is invalid or not recognized.
     */
    function depositToAttachedNFT(uint256 tokenId_, uint256 amount_) external;

    /**
     * @notice Withdraws tokens from an expired lock (non-permanent).
     * @param tokenId_ The ID of the veNFT to withdraw from.
     * @dev Reverts with `AccessDenied` if caller is not owner or approved.
     *      Reverts with `TokenNoExpired` if the lock is not yet expired.
     *      Reverts with `PermanentLocked` if the lock is permanent.
     * Emits a {Withdraw} event and a {Supply} event.
     */
    function withdraw(uint256 tokenId_) external;

    /**
     * @notice Merges one veNFT (tokenFromId_) into another (tokenToId_).
     * @param tokenFromId_ The ID of the source veNFT being merged.
     * @param tokenToId_ The ID of the target veNFT receiving the locked tokens.
     * @dev Reverts with `MergeTokenIdsTheSame` if both IDs are the same.
     *      Reverts with `AccessDenied` if the caller isn't owner or approved for both IDs.
     * Emits a {MergeInit} event at the start, and a {Merge} event upon completion.
     * Also emits a {Deposit} event reflecting the updated lock in the target token.
     */
    function merge(uint256 tokenFromId_, uint256 tokenToId_) external;

    /**
     * @notice Permanently locks a veNFT.
     * @param tokenId_ The ID of the veNFT to be permanently locked.
     * @dev Reverts with `AccessDenied` if caller isn't owner or approved.
     *      Reverts with `TokenAttached` if the token is attached to a managed NFT.
     *      Reverts with `PermanentLocked` if the token already permanent lcoked
     * Emits {LockPermanent} on success.
     */
    function lockPermanent(uint256 tokenId_) external;

    /**
     * @notice Unlocks a permanently locked veNFT, reverting it to a temporary lock.
     * @param tokenId_ The ID of the veNFT to unlock.
     * @dev Reverts with `AccessDenied` if caller isn't owner or approved.
     *      Reverts with `TokenAttached` if the token is attached.
     *      Reverts with `NotPermanentLocked` if the lock isn't actually permanent.
     * Emits {UnlockPermanent} on success.
     */
    function unlockPermanent(uint256 tokenId_) external;

    /**
     * @notice Creates a new managed NFT for a given recipient.
     * @param recipient_ The address that will receive the newly created managed NFT.
     * @return The ID of the newly created managed NFT.
     * @dev Reverts with `AccessDenied` if caller is not the managed NFT manager.
     */
    function createManagedNFT(address recipient_) external returns (uint256);

    /**
     * @notice Attaches a veNFT (user’s token) to a managed NFT, combining their locked balances.
     * @param tokenId_ The ID of the user’s veNFT being attached.
     * @param managedTokenId_ The ID of the managed NFT.
     * @return The amount of tokens locked during the attachment.
     * @dev Reverts with `AccessDenied` if caller is not the managed NFT manager.
     *      Reverts with `ZeroVotingPower` if the user’s token has zero voting power.
     *      Reverts with `NotManagedNft` if the target is not recognized as a managed NFT.
     */
    function onAttachToManagedNFT(uint256 tokenId_, uint256 managedTokenId_) external returns (uint256);

    /**
     * @notice Detaches a veNFT from a managed NFT.
     * @param tokenId_ The ID of the user’s veNFT being detached.
     * @param managedTokenId_ The ID of the managed NFT from which it’s being detached.
     * @param newBalance_ The new locked balance the veNFT will hold after detachment.
     * @dev Reverts with `AccessDenied` if caller is not the managed NFT manager.
     *      Reverts with `NotManagedNft` if the target is not recognized as a managed NFT.
     */
    function onDettachFromManagedNFT(uint256 tokenId_, uint256 managedTokenId_, uint256 newBalance_) external;

    /**
     * @notice Burns a veNEST NFT to reclaim the underlying NEST tokens for use in bribes.
     * @dev Must be called by `customBribeRewardRouter`.
     *      The token must not be permanently locked or attached.
     *      Also resets any votes before burning.
     * Emits a {BurnToBribes} event on successful burn.
     * @param tokenId_ The ID of the veNEST NFT to burn.
     */
    function burnToBribes(uint256 tokenId_) external;
}

Settings
{
  "evmVersion": "paris",
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 2000
  },
  "metadata": {
    "bytecodeHash": "none"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessDenied","type":"error"},{"inputs":[],"name":"AnotherUserTargetLocks","type":"error"},{"inputs":[],"name":"InvalidCompoundEmissionParams","type":"error"},{"inputs":[],"name":"InvalidCreateLockConfig","type":"error"},{"inputs":[],"name":"TargetPoolGaugeIsKilled","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"targetLockFromId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetTokenToId","type":"uint256"}],"name":"ChangeEmissionTargetLock","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CompoundEmissionToBribePool","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CompoundEmissionToTargetLock","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreateLockFromCompoundEmission","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"pool","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreateLockFromCompoundEmissionForBribePools","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"toLocksPercentage","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toBribePoolsPercentage","type":"uint256"}],"name":"SetCompoundEmissionGeneralPercentages","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"uint256","name":"percentage","type":"uint256"}],"indexed":false,"internalType":"struct ICompoundEmissionExtension.TargetPool[]","name":"targetBribePools","type":"tuple[]"}],"name":"SetCompoundEmissionTargetBribePools","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"percentage","type":"uint256"}],"indexed":false,"internalType":"struct ICompoundEmissionExtension.TargetLock[]","name":"targetLocks","type":"tuple[]"}],"name":"SetCompoundEmissionTargetLocks","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"components":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"indexed":false,"internalType":"struct ICompoundEmissionExtension.CreateLockConfig","name":"config","type":"tuple"}],"name":"SetCreateLockConfig","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"indexed":false,"internalType":"struct ICompoundEmissionExtension.CreateLockConfig","name":"config","type":"tuple"}],"name":"SetDefaultCreateLockConfig","type":"event"},{"inputs":[],"name":"COMPOUND_EMISSION_EXTENSION_ADMINISTRATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"COMPOUND_KEEPER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"},{"internalType":"uint256","name":"targetTokenId_","type":"uint256"},{"internalType":"uint256","name":"newTokenId_","type":"uint256"}],"name":"changeEmissionTargetLockId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"address[]","name":"gauges","type":"address[]"},{"components":[{"internalType":"uint256","name":"totalAmount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct IVoter.AggregateClaimBlazeDataParams","name":"blaze","type":"tuple"}],"internalType":"struct ICompoundEmissionExtension.ClaimParams","name":"claimParams_","type":"tuple"}],"name":"compoundEmisisonClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"address[]","name":"gauges","type":"address[]"},{"components":[{"internalType":"uint256","name":"totalAmount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct IVoter.AggregateClaimBlazeDataParams","name":"blaze","type":"tuple"}],"internalType":"struct ICompoundEmissionExtension.ClaimParams[]","name":"claimsParams_","type":"tuple[]"}],"name":"compoundEmissionClaimBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"defaultCreateLockConfig","outputs":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"},{"internalType":"uint256","name":"amountIn_","type":"uint256"}],"name":"getAmountOutToCompound","outputs":[{"internalType":"uint256","name":"toTargetLocks","type":"uint256"},{"internalType":"uint256","name":"toTargetBribePools","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getToBribePoolsPercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getToLocksPercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"}],"name":"getUserCreateLockConfig","outputs":[{"components":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.CreateLockConfig","name":"createLockConfig","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target_","type":"address"}],"name":"getUserInfo","outputs":[{"internalType":"uint256","name":"toLocksPercentage","type":"uint256"},{"internalType":"uint256","name":"toBribePoolsPercentage","type":"uint256"},{"internalType":"bool","name":"isCreateLockCustomConfig","type":"bool"},{"components":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.CreateLockConfig","name":"createLockConfig","type":"tuple"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"percentage","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.TargetLock[]","name":"targetLocks","type":"tuple[]"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"uint256","name":"percentage","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.TargetPool[]","name":"targetBribePools","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"voter_","type":"address"},{"internalType":"address","name":"token_","type":"address"},{"internalType":"address","name":"votingEscrow_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"shouldUpdateGeneralPercentages","type":"bool"},{"internalType":"bool","name":"shouldUpdateTargetLocks","type":"bool"},{"internalType":"bool","name":"shouldUpdateTargetBribePools","type":"bool"},{"internalType":"uint256","name":"toLocksPercentage","type":"uint256"},{"internalType":"uint256","name":"toBribePoolsPercentage","type":"uint256"},{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"percentage","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.TargetLock[]","name":"targetLocks","type":"tuple[]"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"uint256","name":"percentage","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.TargetPool[]","name":"targetsBribePools","type":"tuple[]"}],"internalType":"struct ICompoundEmissionExtension.UpdateCompoundEmissionConfigParams","name":"p_","type":"tuple"}],"name":"setCompoundEmissionConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.CreateLockConfig","name":"config_","type":"tuple"}],"name":"setCreateLockConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bool","name":"shouldBoosted","type":"bool"},{"internalType":"bool","name":"withPermanentLock","type":"bool"},{"internalType":"uint256","name":"lockDuration","type":"uint256"},{"internalType":"uint256","name":"managedTokenIdForAttach","type":"uint256"}],"internalType":"struct ICompoundEmissionExtension.CreateLockConfig","name":"config_","type":"tuple"}],"name":"setDefaultCreateLockConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"voter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"votingEscrow","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

608080604052346100c1576000549060ff8260081c1661006f575060ff80821603610034575b60405161294b90816100c78239f35b60ff90811916176000557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160ff8152a138610025565b62461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b6064820152608490fd5b600080fdfe6080604052600436101561001257600080fd5b60003560e01c8063173f9ea31461148d57806318518521146114355780632618d360146112a557806346c96aac1461127e5780634f2bfe5b14611257578063537afe4d1461121d5780635650ba77146110b55780636386c1c714610e99578063902f039d14610623578063992cb9c3146104c2578063a4015b4c1461046e578063bd6464ce14610433578063bf4cd7ba146103f8578063c0c53b8b146101f8578063c8856fd7146101b7578063fc0c546a14610190578063fe3a04c4146101215763ff88710c146100e257600080fd5b3461011c57602060031936011261011c576001600160a01b036101036115d9565b16600052603a6020526020604060002054604051908152f35b600080fd5b3461011c57604060031936011261011c57604061013c6115d9565b6001600160a01b03602435911690816000526039602052610183670de0b6b3a7640000918261016f8287600020546117fd565b0493600052603a60205284600020546117fd565b0482519182526020820152f35b3461011c57600060031936011261011c5760206001600160a01b0360345416604051908152f35b3461011c57600060031936011261011c5760806036546037546038549060ff604051938181161515855260081c161515602084015260408301526060820152f35b3461011c57606060031936011261011c576102116115d9565b6024356001600160a01b039182821680920361011c576044359083821680920361011c576000549260ff8460081c1615938480956103eb575b80156103d4575b1561036a5784600160ff19831617600055610358575b506000549461028460ff8760081c1661027f81611688565b611688565b600180557fffffffffffffffffffffffff0000000000000000000000000000000000000000921682603354161760335581603454161760345560355416176035556040516102d181611635565b60008152600060208201526000606062eff10092836040820152015261ffff1960365416603655603755600060385561030657005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166000557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a1005b61ffff19166101011760005585610267565b608460405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152fd5b50303b1580156102515750600160ff821614610251565b50600160ff82161061024a565b3461011c57600060031936011261011c5760206040517f4b3b47150674b0de9e350179e12934407a716a86ccb0467b0468ca9fe61053148152f35b3461011c57600060031936011261011c5760206040517f689a3b4b571d498bd544dad28376fcbb1860a3763c569bee21a1bdf2da9580d28152f35b3461011c57602060031936011261011c57608061049161048c6115d9565b6119ec565b6104c0604051809260608091805115158452602081015115156020850152604081015160408501520151910152565bf35b3461011c57608060031936011261011c576104db611706565b1580610619575b8061060f575b80610600575b156105575733600052603b602052604060002060ff198154169055603c602052600060026040822082815582600182015501555b6040517f725f64dac887dde9519cdb1a010e7bb9fe6b8304f8ac9557798b56e9199e366e33918061055281611731565b0390a2005b6044358015806105f1575b6105c75733600052603b6020526040600020600160ff19825416179055603c602052604060002090610592611715565b1515825461ffff1960ff61ff006105a7611706565b151560081b16931691161717825560018201556002606435910155610522565b60046040517f5a4ea98b000000000000000000000000000000000000000000000000000000008152fd5b506105fa611706565b15610562565b50610609611715565b156104ee565b50606435156104e8565b50604435156104e2565b3461011c5760031960208136011261011c5767ffffffffffffffff6004351161011c5760e0906004353603011261011c5760246004350161066381611724565b15610e855761067c60a46004350160043560040161176a565b90505b61068d604460043501611724565b15610e71576106a660c46004350160043560040161176a565b90505b6106b7600435600401611724565b15610e5c5760646004350135915b6106d3600435600401611724565b15610e475760846004350135935b670de0b6b3a76400006106f486866117a0565b11610a8a5783151580928193610e3e575b8115610e23575b50610a8a5784151580938194610e1a575b8115610dff575b50610a8a5761073290611724565b9081610df7575b50610bb2575b61074d604460043501611724565b9081610baa575b506108a2575b610768600435600401611724565b61076e57005b801561083c575b81156107d6575b336000526039602052604060002055603a6020526040600020556040516064600435013581526084600435013560208201527fb585f7689006e053982f09109072da2f28d50b6786f32e09a1b2407ff002ff2660403392a2005b33600052603e6020526107ec6040600020611810565b6108347f928637e2e499fbf8cf92386e95156659014d26214df9547c8b0c7522aead1af261082460c46004350160043560040161176a565b604051339490928392908361196a565b0390a261077c565b33600052603d6020526108526040600020611810565b61089a7f87bbf83c4d9d2750a11d63e9a012db6d64650f43fe4dfbc51f2660e7164fcadb61088a60a46004350160043560040161176a565b6040513394909283929083611914565b0390a2610775565b6033546001600160a01b031691600090815b6108c860c46004350160043560040161176a565b9050831015610abc576108f76108f2846108ec60c46004350160043560040161176a565b906117c3565b611956565b906020610912856108ec60c46004350160043560040161176a565b0135916001600160a01b038116158015610ab4575b610a8a576001600160a01b03604051917faa3f22b80000000000000000000000000000000000000000000000000000000083521660048201526020816024818a5afa908115610a4457600091610a50575b506001600160a01b03604051917f1703e5f90000000000000000000000000000000000000000000000000000000083521660048201526020816024818a5afa908115610a4457600091610a0a575b50156109e0576001916109d8916117a0565b9201916108b4565b60046040517fd9f5b97a000000000000000000000000000000000000000000000000000000008152fd5b90506020813d602011610a3c575b81610a2560209383611665565b8101031261011c57610a36906116f9565b876109c6565b3d9150610a18565b6040513d6000823e3d90fd5b90506020813d602011610a82575b81610a6b60209383611665565b8101031261011c57610a7c906117e9565b87610978565b3d9150610a5e565b60046040517f6ab443c9000000000000000000000000000000000000000000000000000000008152fd5b508215610927565b909350670de0b6b3a7640000915003610a8a57610ae360c46004350160043560040161176a565b9033600052603e602052604060002090610afd838361188e565b9060005260206000206000915b838310610b555750505050610b4d7f928637e2e499fbf8cf92386e95156659014d26214df9547c8b0c7522aead1af261082460c46004350160043560040161176a565b0390a261075a565b60026040826001600160a01b03610b6d600195611956565b167fffffffffffffffffffffffff000000000000000000000000000000000000000086541617855560208101358486015501920192019190610b0a565b905083610754565b90916001600160a01b0360355416906000936000945b610bdc60a46004350160043560040161176a565b9050861015610d25576020610bff876108ec60a46004350160043560040161176a565b0135610c19876108ec60a46004350160043560040161176a565b35610c54575b15610a8a57610c4c6001916020610c44896108ec60a46004350160043560040161176a565b0135906117a0565b950194610bc8565b610c6c876108ec60a46004350160043560040161176a565b35604051907f6352211e0000000000000000000000000000000000000000000000000000000082526004820152602081602481895afa908115610a4457600091610ceb575b506001600160a01b03163314610c1f5760046040517f0b9378da000000000000000000000000000000000000000000000000000000008152fd5b90506020813d602011610d1d575b81610d0660209383611665565b8101031261011c57610d17906117e9565b88610cb1565b3d9150610cf9565b9194509291507ffffffffffffffffffffffffffffffffffffffffffffffffff21f494c589c000001610a8a57610d6560a46004350160043560040161176a565b9033600052603d602052604060002090610d7f838361188e565b9060005260206000206000915b838310610dd75750505050610dcf7f87bbf83c4d9d2750a11d63e9a012db6d64650f43fe4dfbc51f2660e7164fcadb61088a60a46004350160043560040161176a565b0390a261073f565b600260408260019335855560208101358486015501920192019190610d8c565b905084610739565b8615915081610e10575b5086610724565b9050151586610e09565b8015915061071d565b8515915081610e34575b508661070c565b9050151586610e2d565b80159150610705565b33600052603a602052604060002054936106e1565b336000526039602052604060002054916106c5565b33600052603e6020526040600020546106a9565b33600052603d60205260406000205461067f565b3461011c5760208060031936011261011c57610eb36115d9565b90610ebc6119c7565b506001600160a01b0391828116918260005260398152604060002054603a8252610eeb604060002054936119ec565b84600052603d8352604060002091825495610f0587611a62565b96610f136040519889611665565b8088528588018095600052866000206000915b8383106110835750505050600052603b845260ff6040600020541695603e8552604060002095865493610f5885611a62565b94610f666040519687611665565b8086528786018099600052886000206000915b8d84841061104e575050505050610fcf906040519961012093848c01968c52898c0152151560408b015260608a019060608091805115158452602081015115156020850152604081015160408501520151910152565b60e0880152518091526101408601929060005b81811061102f5750505084820361010086015251808252908201929160005b82811061100e5785850386f35b83518051881686528201518583015260409094019392810192600101611001565b8251805186528601518587015260409094019391850191600101610fe2565b8b60019260029260409e9d9e519061106582611603565b86541681528486015483820152815201920192019190999899610f79565b60028960019260409b9a9b5161109881611603565b855481528486015483820152815201920192019190979697610f26565b3461011c57602060031936011261011c5760043567ffffffffffffffff80821161011c573660238301121561011c57816004013590811161011c5760249160058184013684831b840186011161011c57604460206001600160a01b0360335416604051928380927f91d148540000000000000000000000000000000000000000000000000000000082527f4b3b47150674b0de9e350179e12934407a716a86ccb0467b0468ca9fe61053146004830152338b8301525afa908115610a44576000916111e4575b50156111ba57611189611aad565b60005b8481106111995760018055005b806111b46111af88600194871b88010185611a7a565b611b32565b0161118c565b60046040517f4ca88867000000000000000000000000000000000000000000000000000000008152fd5b90506020813d8211611215575b816111fe60209383611665565b8101031261011c5761120f906116f9565b8661117b565b3d91506111f1565b3461011c57602060031936011261011c576001600160a01b0361123e6115d9565b1660005260396020526020604060002054604051908152f35b3461011c57600060031936011261011c5760206001600160a01b0360355416604051908152f35b3461011c57600060031936011261011c5760206001600160a01b0360335416604051908152f35b3461011c57608060031936011261011c57604460206001600160a01b0360335416604051928380927f91d148540000000000000000000000000000000000000000000000000000000082527f689a3b4b571d498bd544dad28376fcbb1860a3763c569bee21a1bdf2da9580d260048301523360248301525afa908115610a44576000916113fc575b50156111ba5761133b611706565b15806113f2575b806113e8575b806113d9575b6105c7576044358015806113ca575b6105c757611369611715565b151560365461ffff1960ff61ff0061137f611706565b151560081b169316911617176036556037556064356038557f2dc661669d339e3e160448267a8c01c178b6193f5cb3b206c57f3732a6da0594604051806113c581611731565b0390a1005b506113d3611706565b1561135d565b506113e2611715565b1561134e565b5060643515611348565b5060443515611342565b90506020813d821161142d575b8161141660209383611665565b8101031261011c57611427906116f9565b8161132d565b3d9150611409565b3461011c5760031960208136011261011c576004359067ffffffffffffffff821161011c5760609082600401923603011261011c5761148790611476611aad565b6111af61148282611956565b61292d565b60018055005b3461011c57606060031936011261011c576114a66115d9565b60243590604435906114b6611aad565b6001600160a01b03906114cc826033541661292d565b16806000526020603981526040600020546114e75760018055005b603d80825260406000209485546114fd81611a62565b9661150b6040519889611665565b818852600090815284812085808a015b8484106115ab57505050505060005b865181101561159b5780826115416001938a611b02565b51511461154f575b0161152a565b8560005283855286611565826040600020611b16565b5055857f184c00bc284cb3ff998f3eed0fd31c5fdbeedca85afe6ef16d2db14715ee3faf604080518681528a89820152a2611549565b5050505050505080808080611487565b6001916002916040516115bd81611603565b855481528486015483820152815201920192019190869061151b565b600435906001600160a01b038216820361011c57565b35906001600160a01b038216820361011c57565b6040810190811067ffffffffffffffff82111761161f57604052565b634e487b7160e01b600052604160045260246000fd5b6080810190811067ffffffffffffffff82111761161f57604052565b67ffffffffffffffff811161161f57604052565b90601f601f19910116810190811067ffffffffffffffff82111761161f57604052565b1561168f57565b608460405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152fd5b5190811515820361011c57565b602435801515810361011c5790565b600435801515810361011c5790565b35801515810361011c5790565b90608082019160043580151580910361011c57815260243580151580910361011c57602082015260443560408201526060606435910152565b903590601e198136030182121561011c570180359067ffffffffffffffff821161011c57602001918160061b3603831361011c57565b919082018092116117ad57565b634e487b7160e01b600052601160045260246000fd5b91908110156117d35760061b0190565b634e487b7160e01b600052603260045260246000fd5b51906001600160a01b038216820361011c57565b818102929181159184041417156117ad57565b8054906000908181558261182357505050565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8316830361187a5781526020812091600190811b8301925b8381106118695750505050565b80836002925583838201550161185c565b602482634e487b7160e01b81526011600452fd5b68010000000000000000821161161f5780548282558083106118af57505050565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80821682036117ad57831683036117ad57600091825260208220600191821b810193821b015b8381106119035750505050565b8083600292558383820155016118f6565b602080825280820184905260409182019390929160005b82811061193a57505050505090565b833586528484013586860152948101949281019260010161192b565b356001600160a01b038116810361011c5790565b602080825280820184905260409182019390929160005b82811061199057505050505090565b90919293959482806001926001600160a01b036119ac896115ef565b168152878a01358a8201529798970196950193929101611981565b604051906119d482611635565b60006060838281528260208201528260408201520152565b6001600160a01b03906119fd6119c7565b5016600052603b60205260ff60406000205416600014611a5b57603c60205260406000205b600260405191611a3183611635565b60ff81548181161515855260081c1615156020840152600181015460408401520154606082015290565b6036611a22565b67ffffffffffffffff811161161f5760051b60200190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18136030182121561011c570190565b600260015414611abe576002600155565b606460405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b80518210156117d35760209160051b010190565b80548210156117d35760005260206000209060011b0190600090565b6001600160a01b0360335416611b4782611956565b906020830135601e1990818536030181121561011c57840180359067ffffffffffffffff821161011c57602001918160051b3603831361011c57611b8e6040870187611a7a565b9180604051967f1e95542c0000000000000000000000000000000000000000000000000000000088526001600160a01b036064890191166004890152606060248901525260848601939060005b8181106125785750505060031985840301604486015281358352602082013560208401526040820135908236030181121561011c57019067ffffffffffffffff82351161011c57602082019180353603831361011c578482601f19601f608094604097606089879901528135606086015281359087860137600086823586010152350116010301816000855af1918215610a4457600090819361253e575b50611c8483826117a0565b15612538576001600160a01b0360345416906001600160a01b036035541692611caf61048c87611956565b91611cba86826117a0565b604051907f23b872dd0000000000000000000000000000000000000000000000000000000060208301528360248301523060448301526064820152606481528060a081011067ffffffffffffffff60a08301111761161f578060a0611d23920160405285612706565b80612277575b5084611d38575b505050505050565b6001600160a01b03611d4987611956565b16600052603e6020526040600020948554611d6381611a62565b96611d716040519889611665565b818852602088019060005260206000206000915b838310612241575050505085519560005b878110611da557505050611d30565b611daf8183611b02565b51906001600160a01b03825116604051907faa3f22b80000000000000000000000000000000000000000000000000000000082526004820152602081602481895afa908115610a4457600091612207575b50670de0b6b3a7640000611e188660208601516117fd565b04906040517f1703e5f90000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526020816024818b5afa8015610a445789916000916121cb575b501561209c57506001600160a01b03604051917fe839663b000000000000000000000000000000000000000000000000000000008352166004820152610100816024818a5afa908115610a4457600091611fb2575b50606001516001600160a01b0316611ed682828b6125a0565b803b1561011c576040517fb66503cf0000000000000000000000000000000000000000000000000000000081526001600160a01b038a16600482015260248101839052906000908290604490829084905af1908115610a4457600194611f996001600160a01b038f611f719082957f89623c97a68fe5cdd5d4d093255dd12eb6f1a4c01666e3a15d062ad042216b9397611fa3575b50611956565b92511694604051938493169583602090939291936001600160a01b0360408201951681520152565b0390a25b01611d96565b611fac90611651565b38611f6b565b610100813d61010011612094575b81611fce6101009383611665565b8101031261209057604051918261010081011067ffffffffffffffff6101008501111761207c5750816060916101006001600160a01b039401604052612013816116f9565b8252612021602082016116f9565b6020830152612032604082016117e9565b60408301526120428382016117e9565b83830152612052608082016117e9565b608083015260a081015160a083015260c0808201519083015260e080910151908201529150611ebd565b80634e487b7160e01b602492526041600452fd5b5080fd5b3d9150611fc0565b939190506120ab818b8b6125a0565b60208c8b6120bd604088015192611956565b8751848901516060909901516040517f42c15c870000000000000000000000000000000000000000000000000000000081526004810188905260248101959095526001600160a01b0392909216604485015215156064840152961515608483015260a482019690965294859060c49082906000905af1918215610a44578c94600093612186575b506001600160a01b037fb3efb2d293d7d90e619d71b4c0cdc02c87368c44a86a4512fc4141924ade02dd9161217e82611f71600199611956565b0390a3611f9d565b91939450916020823d6020116121c3575b816121a460209383611665565b810103126121c05750518b939290916001600160a01b03612144565b80fd5b3d9150612197565b91506020823d6020116121ff575b816121e660209383611665565b810103126121c057506121f989916116f9565b38611e68565b3d91506121d9565b906020823d602011612239575b8161222160209383611665565b810103126121c05750612233906117e9565b38611e00565b3d9150612214565b6002602060019260405161225481611603565b6001600160a01b0386541681528486015483820152815201920192019190611d85565b6122828186866125a0565b6001600160a01b0361229388611956565b16600052603d60205260406000209081546122ad81611a62565b926122bb6040519485611665565b818452602084019060005260206000206000915b83831061250b575050505081519160005b8381106122ef57505050611d29565b6122f98183611b02565b51670de0b6b3a76400006123118560208401516117fd565b048151801560001461246057508760208d8c612331604085015192611956565b8451848601516060909601516040517f42c15c870000000000000000000000000000000000000000000000000000000081526004810189905260248101959095526001600160a01b0392909216604485015215156064840152931515608483015260a482019390935291829060c49082906000905af18015610a44578d9160009161242a575b5060206001600160a01b03612417869594847f32affd6196df3c9436a016339e74fc3bc660962e9f62816e1d5bee46377a6e149560019a9952836123fa83611956565b16600052603d8552612410896040600020611b16565b5055611956565b9451946040519485521692a35b016122e0565b91506020823d602011612458575b8161244560209383611665565b810103126121c05750518c9060206123b7565b3d9150612438565b9291908a3b1561011c5760008b6084829660405197889384927fbdb78cf600000000000000000000000000000000000000000000000000000000845260048401528660248401528160448401528160648401525af1938415610a445760206001600160a01b038f6124fa907fd4612a0ed11d22ae81a5e7a3372dec379d6bbc85813fa8cb55ec7064201351cf94600199611fa35750611956565b9451946040519485521692a3612424565b6002602060019260405161251e81611603565b8554815284860154838201528152019201920191906122cf565b50505050565b92506040833d604011612570575b8161255960409383611665565b810103126121c05750602082519201519138611c79565b3d915061254c565b9091946020806001926001600160a01b036125928a6115ef565b168152019601929101611bdb565b91909181158015612678575b1561260e576040517f095ea7b30000000000000000000000000000000000000000000000000000000060208201526001600160a01b0393909316602484015260448084019290925290825261260c9190612607606483611665565b612706565b565b608460405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e6365000000000000000000006064820152fd5b506040517fdd62ed3e0000000000000000000000000000000000000000000000000000000081523060048201526020816044816001600160a01b03808916602483015286165afa908115610a44576000916126d5575b50156125ac565b906020823d82116126fe575b816126ee60209383611665565b810103126121c0575051386126ce565b3d91506126e1565b6001600160a01b03169060405161271c81611603565b6020928382527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564848301526000808486829651910182855af13d15612853573d9167ffffffffffffffff831161283f57906127979392916040519261278a88601f19601f8401160185611665565b83523d868885013e61285d565b9081519083821592831561281d575b5050509050156127b35750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b8480929394500103126121c057508161283691016116f9565b803883816127a6565b602485634e487b7160e01b81526041600452fd5b9061279792916060915b919290156128be5750815115612871575090565b3b1561287a5790565b606460405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b8251909150156128d15750805190602001fd5b6040519062461bcd60e51b825281602080600483015282519283602484015260005b84811061291657505050601f19601f836000604480968601015201168101030190fd5b8181018301518682016044015285935082016128f3565b6001600160a01b031633036111ba5756fea164736f6c6343000813000a

Deployed Bytecode

0x6080604052600436101561001257600080fd5b60003560e01c8063173f9ea31461148d57806318518521146114355780632618d360146112a557806346c96aac1461127e5780634f2bfe5b14611257578063537afe4d1461121d5780635650ba77146110b55780636386c1c714610e99578063902f039d14610623578063992cb9c3146104c2578063a4015b4c1461046e578063bd6464ce14610433578063bf4cd7ba146103f8578063c0c53b8b146101f8578063c8856fd7146101b7578063fc0c546a14610190578063fe3a04c4146101215763ff88710c146100e257600080fd5b3461011c57602060031936011261011c576001600160a01b036101036115d9565b16600052603a6020526020604060002054604051908152f35b600080fd5b3461011c57604060031936011261011c57604061013c6115d9565b6001600160a01b03602435911690816000526039602052610183670de0b6b3a7640000918261016f8287600020546117fd565b0493600052603a60205284600020546117fd565b0482519182526020820152f35b3461011c57600060031936011261011c5760206001600160a01b0360345416604051908152f35b3461011c57600060031936011261011c5760806036546037546038549060ff604051938181161515855260081c161515602084015260408301526060820152f35b3461011c57606060031936011261011c576102116115d9565b6024356001600160a01b039182821680920361011c576044359083821680920361011c576000549260ff8460081c1615938480956103eb575b80156103d4575b1561036a5784600160ff19831617600055610358575b506000549461028460ff8760081c1661027f81611688565b611688565b600180557fffffffffffffffffffffffff0000000000000000000000000000000000000000921682603354161760335581603454161760345560355416176035556040516102d181611635565b60008152600060208201526000606062eff10092836040820152015261ffff1960365416603655603755600060385561030657005b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166000557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a1005b61ffff19166101011760005585610267565b608460405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152fd5b50303b1580156102515750600160ff821614610251565b50600160ff82161061024a565b3461011c57600060031936011261011c5760206040517f4b3b47150674b0de9e350179e12934407a716a86ccb0467b0468ca9fe61053148152f35b3461011c57600060031936011261011c5760206040517f689a3b4b571d498bd544dad28376fcbb1860a3763c569bee21a1bdf2da9580d28152f35b3461011c57602060031936011261011c57608061049161048c6115d9565b6119ec565b6104c0604051809260608091805115158452602081015115156020850152604081015160408501520151910152565bf35b3461011c57608060031936011261011c576104db611706565b1580610619575b8061060f575b80610600575b156105575733600052603b602052604060002060ff198154169055603c602052600060026040822082815582600182015501555b6040517f725f64dac887dde9519cdb1a010e7bb9fe6b8304f8ac9557798b56e9199e366e33918061055281611731565b0390a2005b6044358015806105f1575b6105c75733600052603b6020526040600020600160ff19825416179055603c602052604060002090610592611715565b1515825461ffff1960ff61ff006105a7611706565b151560081b16931691161717825560018201556002606435910155610522565b60046040517f5a4ea98b000000000000000000000000000000000000000000000000000000008152fd5b506105fa611706565b15610562565b50610609611715565b156104ee565b50606435156104e8565b50604435156104e2565b3461011c5760031960208136011261011c5767ffffffffffffffff6004351161011c5760e0906004353603011261011c5760246004350161066381611724565b15610e855761067c60a46004350160043560040161176a565b90505b61068d604460043501611724565b15610e71576106a660c46004350160043560040161176a565b90505b6106b7600435600401611724565b15610e5c5760646004350135915b6106d3600435600401611724565b15610e475760846004350135935b670de0b6b3a76400006106f486866117a0565b11610a8a5783151580928193610e3e575b8115610e23575b50610a8a5784151580938194610e1a575b8115610dff575b50610a8a5761073290611724565b9081610df7575b50610bb2575b61074d604460043501611724565b9081610baa575b506108a2575b610768600435600401611724565b61076e57005b801561083c575b81156107d6575b336000526039602052604060002055603a6020526040600020556040516064600435013581526084600435013560208201527fb585f7689006e053982f09109072da2f28d50b6786f32e09a1b2407ff002ff2660403392a2005b33600052603e6020526107ec6040600020611810565b6108347f928637e2e499fbf8cf92386e95156659014d26214df9547c8b0c7522aead1af261082460c46004350160043560040161176a565b604051339490928392908361196a565b0390a261077c565b33600052603d6020526108526040600020611810565b61089a7f87bbf83c4d9d2750a11d63e9a012db6d64650f43fe4dfbc51f2660e7164fcadb61088a60a46004350160043560040161176a565b6040513394909283929083611914565b0390a2610775565b6033546001600160a01b031691600090815b6108c860c46004350160043560040161176a565b9050831015610abc576108f76108f2846108ec60c46004350160043560040161176a565b906117c3565b611956565b906020610912856108ec60c46004350160043560040161176a565b0135916001600160a01b038116158015610ab4575b610a8a576001600160a01b03604051917faa3f22b80000000000000000000000000000000000000000000000000000000083521660048201526020816024818a5afa908115610a4457600091610a50575b506001600160a01b03604051917f1703e5f90000000000000000000000000000000000000000000000000000000083521660048201526020816024818a5afa908115610a4457600091610a0a575b50156109e0576001916109d8916117a0565b9201916108b4565b60046040517fd9f5b97a000000000000000000000000000000000000000000000000000000008152fd5b90506020813d602011610a3c575b81610a2560209383611665565b8101031261011c57610a36906116f9565b876109c6565b3d9150610a18565b6040513d6000823e3d90fd5b90506020813d602011610a82575b81610a6b60209383611665565b8101031261011c57610a7c906117e9565b87610978565b3d9150610a5e565b60046040517f6ab443c9000000000000000000000000000000000000000000000000000000008152fd5b508215610927565b909350670de0b6b3a7640000915003610a8a57610ae360c46004350160043560040161176a565b9033600052603e602052604060002090610afd838361188e565b9060005260206000206000915b838310610b555750505050610b4d7f928637e2e499fbf8cf92386e95156659014d26214df9547c8b0c7522aead1af261082460c46004350160043560040161176a565b0390a261075a565b60026040826001600160a01b03610b6d600195611956565b167fffffffffffffffffffffffff000000000000000000000000000000000000000086541617855560208101358486015501920192019190610b0a565b905083610754565b90916001600160a01b0360355416906000936000945b610bdc60a46004350160043560040161176a565b9050861015610d25576020610bff876108ec60a46004350160043560040161176a565b0135610c19876108ec60a46004350160043560040161176a565b35610c54575b15610a8a57610c4c6001916020610c44896108ec60a46004350160043560040161176a565b0135906117a0565b950194610bc8565b610c6c876108ec60a46004350160043560040161176a565b35604051907f6352211e0000000000000000000000000000000000000000000000000000000082526004820152602081602481895afa908115610a4457600091610ceb575b506001600160a01b03163314610c1f5760046040517f0b9378da000000000000000000000000000000000000000000000000000000008152fd5b90506020813d602011610d1d575b81610d0660209383611665565b8101031261011c57610d17906117e9565b88610cb1565b3d9150610cf9565b9194509291507ffffffffffffffffffffffffffffffffffffffffffffffffff21f494c589c000001610a8a57610d6560a46004350160043560040161176a565b9033600052603d602052604060002090610d7f838361188e565b9060005260206000206000915b838310610dd75750505050610dcf7f87bbf83c4d9d2750a11d63e9a012db6d64650f43fe4dfbc51f2660e7164fcadb61088a60a46004350160043560040161176a565b0390a261073f565b600260408260019335855560208101358486015501920192019190610d8c565b905084610739565b8615915081610e10575b5086610724565b9050151586610e09565b8015915061071d565b8515915081610e34575b508661070c565b9050151586610e2d565b80159150610705565b33600052603a602052604060002054936106e1565b336000526039602052604060002054916106c5565b33600052603e6020526040600020546106a9565b33600052603d60205260406000205461067f565b3461011c5760208060031936011261011c57610eb36115d9565b90610ebc6119c7565b506001600160a01b0391828116918260005260398152604060002054603a8252610eeb604060002054936119ec565b84600052603d8352604060002091825495610f0587611a62565b96610f136040519889611665565b8088528588018095600052866000206000915b8383106110835750505050600052603b845260ff6040600020541695603e8552604060002095865493610f5885611a62565b94610f666040519687611665565b8086528786018099600052886000206000915b8d84841061104e575050505050610fcf906040519961012093848c01968c52898c0152151560408b015260608a019060608091805115158452602081015115156020850152604081015160408501520151910152565b60e0880152518091526101408601929060005b81811061102f5750505084820361010086015251808252908201929160005b82811061100e5785850386f35b83518051881686528201518583015260409094019392810192600101611001565b8251805186528601518587015260409094019391850191600101610fe2565b8b60019260029260409e9d9e519061106582611603565b86541681528486015483820152815201920192019190999899610f79565b60028960019260409b9a9b5161109881611603565b855481528486015483820152815201920192019190979697610f26565b3461011c57602060031936011261011c5760043567ffffffffffffffff80821161011c573660238301121561011c57816004013590811161011c5760249160058184013684831b840186011161011c57604460206001600160a01b0360335416604051928380927f91d148540000000000000000000000000000000000000000000000000000000082527f4b3b47150674b0de9e350179e12934407a716a86ccb0467b0468ca9fe61053146004830152338b8301525afa908115610a44576000916111e4575b50156111ba57611189611aad565b60005b8481106111995760018055005b806111b46111af88600194871b88010185611a7a565b611b32565b0161118c565b60046040517f4ca88867000000000000000000000000000000000000000000000000000000008152fd5b90506020813d8211611215575b816111fe60209383611665565b8101031261011c5761120f906116f9565b8661117b565b3d91506111f1565b3461011c57602060031936011261011c576001600160a01b0361123e6115d9565b1660005260396020526020604060002054604051908152f35b3461011c57600060031936011261011c5760206001600160a01b0360355416604051908152f35b3461011c57600060031936011261011c5760206001600160a01b0360335416604051908152f35b3461011c57608060031936011261011c57604460206001600160a01b0360335416604051928380927f91d148540000000000000000000000000000000000000000000000000000000082527f689a3b4b571d498bd544dad28376fcbb1860a3763c569bee21a1bdf2da9580d260048301523360248301525afa908115610a44576000916113fc575b50156111ba5761133b611706565b15806113f2575b806113e8575b806113d9575b6105c7576044358015806113ca575b6105c757611369611715565b151560365461ffff1960ff61ff0061137f611706565b151560081b169316911617176036556037556064356038557f2dc661669d339e3e160448267a8c01c178b6193f5cb3b206c57f3732a6da0594604051806113c581611731565b0390a1005b506113d3611706565b1561135d565b506113e2611715565b1561134e565b5060643515611348565b5060443515611342565b90506020813d821161142d575b8161141660209383611665565b8101031261011c57611427906116f9565b8161132d565b3d9150611409565b3461011c5760031960208136011261011c576004359067ffffffffffffffff821161011c5760609082600401923603011261011c5761148790611476611aad565b6111af61148282611956565b61292d565b60018055005b3461011c57606060031936011261011c576114a66115d9565b60243590604435906114b6611aad565b6001600160a01b03906114cc826033541661292d565b16806000526020603981526040600020546114e75760018055005b603d80825260406000209485546114fd81611a62565b9661150b6040519889611665565b818852600090815284812085808a015b8484106115ab57505050505060005b865181101561159b5780826115416001938a611b02565b51511461154f575b0161152a565b8560005283855286611565826040600020611b16565b5055857f184c00bc284cb3ff998f3eed0fd31c5fdbeedca85afe6ef16d2db14715ee3faf604080518681528a89820152a2611549565b5050505050505080808080611487565b6001916002916040516115bd81611603565b855481528486015483820152815201920192019190869061151b565b600435906001600160a01b038216820361011c57565b35906001600160a01b038216820361011c57565b6040810190811067ffffffffffffffff82111761161f57604052565b634e487b7160e01b600052604160045260246000fd5b6080810190811067ffffffffffffffff82111761161f57604052565b67ffffffffffffffff811161161f57604052565b90601f601f19910116810190811067ffffffffffffffff82111761161f57604052565b1561168f57565b608460405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152fd5b5190811515820361011c57565b602435801515810361011c5790565b600435801515810361011c5790565b35801515810361011c5790565b90608082019160043580151580910361011c57815260243580151580910361011c57602082015260443560408201526060606435910152565b903590601e198136030182121561011c570180359067ffffffffffffffff821161011c57602001918160061b3603831361011c57565b919082018092116117ad57565b634e487b7160e01b600052601160045260246000fd5b91908110156117d35760061b0190565b634e487b7160e01b600052603260045260246000fd5b51906001600160a01b038216820361011c57565b818102929181159184041417156117ad57565b8054906000908181558261182357505050565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8316830361187a5781526020812091600190811b8301925b8381106118695750505050565b80836002925583838201550161185c565b602482634e487b7160e01b81526011600452fd5b68010000000000000000821161161f5780548282558083106118af57505050565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80821682036117ad57831683036117ad57600091825260208220600191821b810193821b015b8381106119035750505050565b8083600292558383820155016118f6565b602080825280820184905260409182019390929160005b82811061193a57505050505090565b833586528484013586860152948101949281019260010161192b565b356001600160a01b038116810361011c5790565b602080825280820184905260409182019390929160005b82811061199057505050505090565b90919293959482806001926001600160a01b036119ac896115ef565b168152878a01358a8201529798970196950193929101611981565b604051906119d482611635565b60006060838281528260208201528260408201520152565b6001600160a01b03906119fd6119c7565b5016600052603b60205260ff60406000205416600014611a5b57603c60205260406000205b600260405191611a3183611635565b60ff81548181161515855260081c1615156020840152600181015460408401520154606082015290565b6036611a22565b67ffffffffffffffff811161161f5760051b60200190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18136030182121561011c570190565b600260015414611abe576002600155565b606460405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b80518210156117d35760209160051b010190565b80548210156117d35760005260206000209060011b0190600090565b6001600160a01b0360335416611b4782611956565b906020830135601e1990818536030181121561011c57840180359067ffffffffffffffff821161011c57602001918160051b3603831361011c57611b8e6040870187611a7a565b9180604051967f1e95542c0000000000000000000000000000000000000000000000000000000088526001600160a01b036064890191166004890152606060248901525260848601939060005b8181106125785750505060031985840301604486015281358352602082013560208401526040820135908236030181121561011c57019067ffffffffffffffff82351161011c57602082019180353603831361011c578482601f19601f608094604097606089879901528135606086015281359087860137600086823586010152350116010301816000855af1918215610a4457600090819361253e575b50611c8483826117a0565b15612538576001600160a01b0360345416906001600160a01b036035541692611caf61048c87611956565b91611cba86826117a0565b604051907f23b872dd0000000000000000000000000000000000000000000000000000000060208301528360248301523060448301526064820152606481528060a081011067ffffffffffffffff60a08301111761161f578060a0611d23920160405285612706565b80612277575b5084611d38575b505050505050565b6001600160a01b03611d4987611956565b16600052603e6020526040600020948554611d6381611a62565b96611d716040519889611665565b818852602088019060005260206000206000915b838310612241575050505085519560005b878110611da557505050611d30565b611daf8183611b02565b51906001600160a01b03825116604051907faa3f22b80000000000000000000000000000000000000000000000000000000082526004820152602081602481895afa908115610a4457600091612207575b50670de0b6b3a7640000611e188660208601516117fd565b04906040517f1703e5f90000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526020816024818b5afa8015610a445789916000916121cb575b501561209c57506001600160a01b03604051917fe839663b000000000000000000000000000000000000000000000000000000008352166004820152610100816024818a5afa908115610a4457600091611fb2575b50606001516001600160a01b0316611ed682828b6125a0565b803b1561011c576040517fb66503cf0000000000000000000000000000000000000000000000000000000081526001600160a01b038a16600482015260248101839052906000908290604490829084905af1908115610a4457600194611f996001600160a01b038f611f719082957f89623c97a68fe5cdd5d4d093255dd12eb6f1a4c01666e3a15d062ad042216b9397611fa3575b50611956565b92511694604051938493169583602090939291936001600160a01b0360408201951681520152565b0390a25b01611d96565b611fac90611651565b38611f6b565b610100813d61010011612094575b81611fce6101009383611665565b8101031261209057604051918261010081011067ffffffffffffffff6101008501111761207c5750816060916101006001600160a01b039401604052612013816116f9565b8252612021602082016116f9565b6020830152612032604082016117e9565b60408301526120428382016117e9565b83830152612052608082016117e9565b608083015260a081015160a083015260c0808201519083015260e080910151908201529150611ebd565b80634e487b7160e01b602492526041600452fd5b5080fd5b3d9150611fc0565b939190506120ab818b8b6125a0565b60208c8b6120bd604088015192611956565b8751848901516060909901516040517f42c15c870000000000000000000000000000000000000000000000000000000081526004810188905260248101959095526001600160a01b0392909216604485015215156064840152961515608483015260a482019690965294859060c49082906000905af1918215610a44578c94600093612186575b506001600160a01b037fb3efb2d293d7d90e619d71b4c0cdc02c87368c44a86a4512fc4141924ade02dd9161217e82611f71600199611956565b0390a3611f9d565b91939450916020823d6020116121c3575b816121a460209383611665565b810103126121c05750518b939290916001600160a01b03612144565b80fd5b3d9150612197565b91506020823d6020116121ff575b816121e660209383611665565b810103126121c057506121f989916116f9565b38611e68565b3d91506121d9565b906020823d602011612239575b8161222160209383611665565b810103126121c05750612233906117e9565b38611e00565b3d9150612214565b6002602060019260405161225481611603565b6001600160a01b0386541681528486015483820152815201920192019190611d85565b6122828186866125a0565b6001600160a01b0361229388611956565b16600052603d60205260406000209081546122ad81611a62565b926122bb6040519485611665565b818452602084019060005260206000206000915b83831061250b575050505081519160005b8381106122ef57505050611d29565b6122f98183611b02565b51670de0b6b3a76400006123118560208401516117fd565b048151801560001461246057508760208d8c612331604085015192611956565b8451848601516060909601516040517f42c15c870000000000000000000000000000000000000000000000000000000081526004810189905260248101959095526001600160a01b0392909216604485015215156064840152931515608483015260a482019390935291829060c49082906000905af18015610a44578d9160009161242a575b5060206001600160a01b03612417869594847f32affd6196df3c9436a016339e74fc3bc660962e9f62816e1d5bee46377a6e149560019a9952836123fa83611956565b16600052603d8552612410896040600020611b16565b5055611956565b9451946040519485521692a35b016122e0565b91506020823d602011612458575b8161244560209383611665565b810103126121c05750518c9060206123b7565b3d9150612438565b9291908a3b1561011c5760008b6084829660405197889384927fbdb78cf600000000000000000000000000000000000000000000000000000000845260048401528660248401528160448401528160648401525af1938415610a445760206001600160a01b038f6124fa907fd4612a0ed11d22ae81a5e7a3372dec379d6bbc85813fa8cb55ec7064201351cf94600199611fa35750611956565b9451946040519485521692a3612424565b6002602060019260405161251e81611603565b8554815284860154838201528152019201920191906122cf565b50505050565b92506040833d604011612570575b8161255960409383611665565b810103126121c05750602082519201519138611c79565b3d915061254c565b9091946020806001926001600160a01b036125928a6115ef565b168152019601929101611bdb565b91909181158015612678575b1561260e576040517f095ea7b30000000000000000000000000000000000000000000000000000000060208201526001600160a01b0393909316602484015260448084019290925290825261260c9190612607606483611665565b612706565b565b608460405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e6365000000000000000000006064820152fd5b506040517fdd62ed3e0000000000000000000000000000000000000000000000000000000081523060048201526020816044816001600160a01b03808916602483015286165afa908115610a44576000916126d5575b50156125ac565b906020823d82116126fe575b816126ee60209383611665565b810103126121c0575051386126ce565b3d91506126e1565b6001600160a01b03169060405161271c81611603565b6020928382527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564848301526000808486829651910182855af13d15612853573d9167ffffffffffffffff831161283f57906127979392916040519261278a88601f19601f8401160185611665565b83523d868885013e61285d565b9081519083821592831561281d575b5050509050156127b35750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152fd5b8480929394500103126121c057508161283691016116f9565b803883816127a6565b602485634e487b7160e01b81526041600452fd5b9061279792916060915b919290156128be5750815115612871575090565b3b1561287a5790565b606460405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152fd5b8251909150156128d15750805190602001fd5b6040519062461bcd60e51b825281602080600483015282519283602484015260005b84811061291657505050601f19601f836000604480968601015201168101030190fd5b8181018301518682016044015285935082016128f3565b6001600160a01b031633036111ba5756fea164736f6c6343000813000a

Block Transaction Gas Used Reward
view all blocks ##produced##

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.