VESystemSetup

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/VESystemSetup.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import { IDAO } from "@aragon/osx-commons-contracts/src/dao/IDAO.sol";
import { PluginSetup } from "@aragon/osx-commons-contracts/src/plugin/setup/PluginSetup.sol";
import { IPluginSetup } from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol";
import { PermissionLib } from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol";
import { ProxyLib } from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol";

import { VotingEscrowV1_2_0 as VotingEscrow } from "@escrow/VotingEscrowIncreasing_v1_2_0.sol";
import { ClockV1_2_0 as Clock } from "@clock/Clock_v1_2_0.sol";
import { LockV1_2_0 as Lock } from "@lock/Lock_v1_2_0.sol";
import { LinearIncreasingCurve as Curve } from "@curve/LinearIncreasingCurve.sol";
import { DynamicExitQueue as ExitQueue } from "@queue/DynamicExitQueue.sol";
import { SelfDelegationEscrowIVotesAdapter } from "@delegation/SelfDelegationEscrowIVotesAdapter.sol";
import { EscrowIVotesAdapter } from "@delegation/EscrowIVotesAdapter.sol";
import { AddressGaugeVoter } from "@voting/AddressGaugeVoter.sol";

/// @notice Parameters for VE system deployment
/// @param underlyingToken The ERC20 token to lock in the VotingEscrow
/// @param veTokenName The name for the veToken NFT
/// @param veTokenSymbol The symbol for the veToken NFT
/// @param minDeposit The minimum amount of tokens required to create a lock
/// @param minLockDuration Min seconds a user must have locked before they can queue an exit
/// @param feePercent The fee taken on withdrawals (1 ether = 100%)
/// @param cooldownPeriod Delay seconds after queuing an exit before withdrawing becomes possible
/// @param curveConstant Constant coefficient for voting power curve (1e18 for flat curve)
/// @param curveLinear Linear coefficient for voting power curve (0 for flat curve)
/// @param curveQuadratic Quadratic coefficient for voting power curve (0 for flat curve)
/// @param curveMaxEpochs Maximum epochs for curve (0 for flat curve)
struct VESystemSetupParams {
  address underlyingToken;
  string veTokenName;
  string veTokenSymbol;
  uint256 minDeposit;
  uint48 minLockDuration;
  uint16 feePercent;
  uint48 cooldownPeriod;
  int256 curveConstant;
  int256 curveLinear;
  int256 curveQuadratic;
  uint48 curveMaxEpochs;
}

/// @notice PluginSetup for deploying a complete VE (Voting Escrow) system
/// @dev This setup deploys all VE components: VotingEscrow, Clock, Curve, ExitQueue, Lock, and IVotesAdapter
contract VESystemSetup is PluginSetup {
  using ProxyLib for address;

  /// @notice Thrown if passed helpers array is of wrong length
  error WrongHelpersArrayLength(uint256 length);

  /// @notice Base implementation contracts (deployed separately, reused via proxies)
  address public immutable clockBase;
  address public immutable escrowBase;
  address public immutable curveBase;
  address public immutable queueBase;
  address public immutable nftBase;
  address public immutable ivotesAdapterBase;
  address public immutable voterBase;

  /// @notice Initializes VESystemSetup with pre-deployed base implementation contracts
  /// @param _clockBase Address of deployed Clock implementation
  /// @param _escrowBase Address of deployed VotingEscrow implementation
  /// @param _curveBase Address of deployed Curve implementation (with specific curve params)
  /// @param _queueBase Address of deployed ExitQueue implementation
  /// @param _nftBase Address of deployed Lock implementation
  /// @param _ivotesAdapterBase Address of deployed IVotesAdapter implementation (with specific curve params)
  /// @param _voterBase Address of deployed AddressGaugeVoter implementation
  constructor(
    address _clockBase,
    address _escrowBase,
    address _curveBase,
    address _queueBase,
    address _nftBase,
    address _ivotesAdapterBase,
    address _voterBase
  ) PluginSetup(_escrowBase) {
    clockBase = _clockBase;
    escrowBase = _escrowBase;
    curveBase = _curveBase;
    queueBase = _queueBase;
    nftBase = _nftBase;
    ivotesAdapterBase = _ivotesAdapterBase;
    voterBase = _voterBase;
  }

  /// @notice Prepares the installation of a VE system
  /// @param _dao The DAO address
  /// @param _data The encoded VESystemSetupParams
  /// @return plugin The "plugin" address (VotingEscrow contract)
  /// @return preparedSetupData The prepared setup data including helpers and permissions
  function prepareInstallation(address _dao, bytes calldata _data)
    external
    returns (address plugin, PreparedSetupData memory preparedSetupData)
  {
    VESystemSetupParams memory params = abi.decode(_data, (VESystemSetupParams));
    address dao = address(_dao);

    // Deploy all VE components as proxies
    address clockProxy = clockBase.deployUUPSProxy(abi.encodeCall(Clock.initialize, (dao)));

    address escrowProxy = escrowBase.deployUUPSProxy(
      abi.encodeCall(VotingEscrow.initialize, (params.underlyingToken, dao, clockProxy, params.minDeposit))
    );

    address curveProxy = curveBase.deployUUPSProxy(abi.encodeCall(Curve.initialize, (escrowProxy, dao, clockProxy)));

    address queueProxy = queueBase.deployUUPSProxy(
      abi.encodeCall(
        ExitQueue.initialize,
        (escrowProxy, params.cooldownPeriod, dao, params.feePercent, clockProxy, params.minLockDuration)
      )
    );

    address nftProxy = nftBase.deployUUPSProxy(
      abi.encodeCall(Lock.initialize, (escrowProxy, params.veTokenName, params.veTokenSymbol, dao))
    );

    address adapterProxy = ivotesAdapterBase.deployUUPSProxy(
      abi.encodeCall(EscrowIVotesAdapter.initialize, (dao, escrowProxy, clockProxy, false))
    );

    address voterProxy = voterBase.deployUUPSProxy(
      abi.encodeCall(AddressGaugeVoter.initialize, (dao, escrowProxy, false, clockProxy, adapterProxy, true))
    );

    // NOTE: Components are NOT wired together here - the factory will do that
    // after granting itself the necessary permissions

    // The "plugin" for OSx purposes is the VotingEscrow (main entry point)
    plugin = escrowProxy;

    // Return all components as helpers (factory will need them for permissions)
    address[] memory helpers = new address[](6);
    helpers[0] = clockProxy; // index 0: Clock
    helpers[1] = curveProxy; // index 1: Curve
    helpers[2] = queueProxy; // index 2: ExitQueue
    helpers[3] = nftProxy; // index 3: Lock
    helpers[4] = adapterProxy; // index 4: IVotesAdapter
    helpers[5] = voterProxy; // index 5: AddressGaugeVoter

    // Define permissions that need to be granted
    PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[](15);

    // VotingEscrow permissions (plugin is escrow)
    permissions[0] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: plugin,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(plugin).ESCROW_ADMIN_ROLE()
    });

    permissions[1] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: plugin,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(plugin).PAUSER_ROLE()
    });

    permissions[2] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: plugin,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(plugin).SWEEPER_ROLE()
    });

    // Curve permissions
    permissions[3] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: curveProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: Curve(curveProxy).CURVE_ADMIN_ROLE()
    });

    // ExitQueue permissions
    permissions[4] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: queueProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: ExitQueue(queueProxy).QUEUE_ADMIN_ROLE()
    });

    permissions[5] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: queueProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: ExitQueue(queueProxy).WITHDRAW_ROLE()
    });

    // Lock permissions
    permissions[6] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: nftProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: Lock(nftProxy).LOCK_ADMIN_ROLE()
    });

    // IVotesAdapter permissions
    permissions[7] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: adapterProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: SelfDelegationEscrowIVotesAdapter(adapterProxy).DELEGATION_ADMIN_ROLE()
    });

    permissions[8] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: adapterProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: SelfDelegationEscrowIVotesAdapter(adapterProxy).DELEGATION_TOKEN_ROLE()
    });

    // Component cross-permissions (components need to call each other)
    permissions[9] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: plugin,
      who: queueProxy,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(plugin).ESCROW_ADMIN_ROLE()
    });

    permissions[10] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: plugin,
      who: nftProxy,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(plugin).ESCROW_ADMIN_ROLE()
    });

    permissions[11] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: nftProxy,
      who: plugin,
      condition: PermissionLib.NO_CONDITION,
      permissionId: Lock(nftProxy).LOCK_ADMIN_ROLE()
    });

    permissions[12] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: adapterProxy,
      who: plugin,
      condition: PermissionLib.NO_CONDITION,
      permissionId: SelfDelegationEscrowIVotesAdapter(adapterProxy).DELEGATION_TOKEN_ROLE()
    });

    // AddressGaugeVoter permissions
    permissions[13] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: voterProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: AddressGaugeVoter(voterProxy).GAUGE_ADMIN_ROLE()
    });

    permissions[14] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Grant,
      where: voterProxy,
      who: dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: keccak256("EXECUTE_PERMISSION")
    });

    preparedSetupData = PreparedSetupData({ helpers: helpers, permissions: permissions });
  }

  /// @notice Prepares the uninstallation of a VE system
  /// @param _dao The DAO address
  /// @param _payload The uninstallation payload
  /// @return permissions The permissions to be revoked
  function prepareUninstallation(address _dao, SetupPayload calldata _payload)
    external
    view
    returns (PermissionLib.MultiTargetPermission[] memory permissions)
  {
    if (_payload.currentHelpers.length != 6) {
      revert WrongHelpersArrayLength(_payload.currentHelpers.length);
    }

    address escrowProxy = _payload.plugin;
    // clockProxy = _payload.currentHelpers[0] - not needed, Clock has no permissions to revoke
    address curveProxy = _payload.currentHelpers[1];
    address queueProxy = _payload.currentHelpers[2];
    address nftProxy = _payload.currentHelpers[3];
    address adapterProxy = _payload.currentHelpers[4];
    address voterProxy = _payload.currentHelpers[5];

    // Revoke all permissions granted during installation
    permissions = new PermissionLib.MultiTargetPermission[](15);

    // Revoke VotingEscrow permissions
    permissions[0] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: escrowProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(escrowProxy).ESCROW_ADMIN_ROLE()
    });

    permissions[1] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: escrowProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(escrowProxy).PAUSER_ROLE()
    });

    permissions[2] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: escrowProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(escrowProxy).SWEEPER_ROLE()
    });

    // Revoke Curve permissions
    permissions[3] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: curveProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: Curve(curveProxy).CURVE_ADMIN_ROLE()
    });

    // Revoke ExitQueue permissions
    permissions[4] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: queueProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: ExitQueue(queueProxy).QUEUE_ADMIN_ROLE()
    });

    permissions[5] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: queueProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: ExitQueue(queueProxy).WITHDRAW_ROLE()
    });

    // Revoke Lock permissions
    permissions[6] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: nftProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: Lock(nftProxy).LOCK_ADMIN_ROLE()
    });

    // Revoke IVotesAdapter permissions
    permissions[7] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: adapterProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: SelfDelegationEscrowIVotesAdapter(adapterProxy).DELEGATION_ADMIN_ROLE()
    });

    permissions[8] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: adapterProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: SelfDelegationEscrowIVotesAdapter(adapterProxy).DELEGATION_TOKEN_ROLE()
    });

    // Revoke component cross-permissions
    permissions[9] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: escrowProxy,
      who: queueProxy,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(escrowProxy).ESCROW_ADMIN_ROLE()
    });

    permissions[10] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: escrowProxy,
      who: nftProxy,
      condition: PermissionLib.NO_CONDITION,
      permissionId: VotingEscrow(escrowProxy).ESCROW_ADMIN_ROLE()
    });

    permissions[11] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: nftProxy,
      who: escrowProxy,
      condition: PermissionLib.NO_CONDITION,
      permissionId: Lock(nftProxy).LOCK_ADMIN_ROLE()
    });

    permissions[12] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: adapterProxy,
      who: escrowProxy,
      condition: PermissionLib.NO_CONDITION,
      permissionId: SelfDelegationEscrowIVotesAdapter(adapterProxy).DELEGATION_TOKEN_ROLE()
    });

    // Revoke AddressGaugeVoter permissions
    permissions[13] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: voterProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: AddressGaugeVoter(voterProxy).GAUGE_ADMIN_ROLE()
    });

    permissions[14] = PermissionLib.MultiTargetPermission({
      operation: PermissionLib.Operation.Revoke,
      where: voterProxy,
      who: _dao,
      condition: PermissionLib.NO_CONDITION,
      permissionId: keccak256("EXECUTE_PERMISSION")
    });
  }
}
"
    },
    "lib/ve-governance/lib/osx-commons/contracts/src/dao/IDAO.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

/// @title IDAO
/// @author Aragon X - 2022-2024
/// @notice The interface required for DAOs within the Aragon App DAO framework.
/// @custom:security-contact sirt@aragon.org
interface IDAO {
    /// @notice Checks if an address has permission on a contract via a permission identifier and considers if `ANY_ADDRESS` was used in the granting process.
    /// @param _where The address of the contract.
    /// @param _who The address of a EOA or contract to give the permissions.
    /// @param _permissionId The permission identifier.
    /// @param _data The optional data passed to the `PermissionCondition` registered.
    /// @return Returns true if the address has permission, false if not.
    function hasPermission(
        address _where,
        address _who,
        bytes32 _permissionId,
        bytes memory _data
    ) external view returns (bool);

    /// @notice Updates the DAO metadata (e.g., an IPFS hash).
    /// @param _metadata The IPFS hash of the new metadata object.
    function setMetadata(bytes calldata _metadata) external;

    /// @notice Emitted when the DAO metadata is updated.
    /// @param metadata The IPFS hash of the new metadata object.
    event MetadataSet(bytes metadata);

    /// @notice Emitted when a standard callback is registered.
    /// @param interfaceId The ID of the interface.
    /// @param callbackSelector The selector of the callback function.
    /// @param magicNumber The magic number to be registered for the callback function selector.
    event StandardCallbackRegistered(
        bytes4 interfaceId,
        bytes4 callbackSelector,
        bytes4 magicNumber
    );

    /// @notice Deposits (native) tokens to the DAO contract with a reference string.
    /// @param _token The address of the token or address(0) in case of the native token.
    /// @param _amount The amount of tokens to deposit.
    /// @param _reference The reference describing the deposit reason.
    function deposit(address _token, uint256 _amount, string calldata _reference) external payable;

    /// @notice Emitted when a token deposit has been made to the DAO.
    /// @param sender The address of the sender.
    /// @param token The address of the deposited token.
    /// @param amount The amount of tokens deposited.
    /// @param _reference The reference describing the deposit reason.
    event Deposited(
        address indexed sender,
        address indexed token,
        uint256 amount,
        string _reference
    );

    /// @notice Emitted when a native token deposit has been made to the DAO.
    /// @dev This event is intended to be emitted in the `receive` function and is therefore bound by the gas limitations for `send`/`transfer` calls introduced by [ERC-2929](https://eips.ethereum.org/EIPS/eip-2929).
    /// @param sender The address of the sender.
    /// @param amount The amount of native tokens deposited.
    event NativeTokenDeposited(address sender, uint256 amount);

    /// @notice Setter for the trusted forwarder verifying the meta transaction.
    /// @param _trustedForwarder The trusted forwarder address.
    function setTrustedForwarder(address _trustedForwarder) external;

    /// @notice Getter for the trusted forwarder verifying the meta transaction.
    /// @return The trusted forwarder address.
    function getTrustedForwarder() external view returns (address);

    /// @notice Emitted when a new TrustedForwarder is set on the DAO.
    /// @param forwarder the new forwarder address.
    event TrustedForwarderSet(address forwarder);

    /// @notice Checks whether a signature is valid for a provided hash according to [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271).
    /// @param _hash The hash of the data to be signed.
    /// @param _signature The signature byte array associated with `_hash`.
    /// @return Returns the `bytes4` magic value `0x1626ba7e` if the signature is valid and `0xffffffff` if not.
    function isValidSignature(bytes32 _hash, bytes memory _signature) external returns (bytes4);

    /// @notice Registers an ERC standard having a callback by registering its [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID and callback function signature.
    /// @param _interfaceId The ID of the interface.
    /// @param _callbackSelector The selector of the callback function.
    /// @param _magicNumber The magic number to be registered for the function signature.
    function registerStandardCallback(
        bytes4 _interfaceId,
        bytes4 _callbackSelector,
        bytes4 _magicNumber
    ) external;

    /// @notice Removed function being left here to not corrupt the IDAO interface ID. Any call will revert.
    /// @dev Introduced in v1.0.0. Removed in v1.4.0.
    function setSignatureValidator(address) external;
}
"
    },
    "lib/ve-governance/lib/osx-commons/contracts/src/plugin/setup/PluginSetup.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

import {IProtocolVersion} from "../../utils/versioning/IProtocolVersion.sol";
import {ProtocolVersion} from "../../utils/versioning/ProtocolVersion.sol";
import {IPluginSetup} from "./IPluginSetup.sol";

/// @title PluginSetup
/// @author Aragon X - 2022-2024
/// @notice An abstract contract to inherit from to implement the plugin setup for non-upgradeable plugins, i.e,
/// - `Plugin` being deployed via the `new` keyword
/// - `PluginCloneable` being deployed via the minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)).
/// @custom:security-contact sirt@aragon.org
abstract contract PluginSetup is ERC165, IPluginSetup, ProtocolVersion {
    /// @notice The address of the plugin implementation contract for initial block explorer verification and, in the case of `PluginClonable` implementations, to create [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) clones from.
    address internal immutable IMPLEMENTATION;

    /// @notice Thrown when attempting to prepare an update on a non-upgradeable plugin.
    error NonUpgradeablePlugin();

    /// @notice The contract constructor, that setting the plugin implementation contract.
    /// @param _implementation The address of the plugin implementation contract.
    constructor(address _implementation) {
        IMPLEMENTATION = _implementation;
    }

    /// @inheritdoc IPluginSetup
    /// @dev Since the underlying plugin is non-upgradeable, this non-virtual function must always revert.
    function prepareUpdate(
        address _dao,
        uint16 _fromBuild,
        SetupPayload calldata _payload
    ) external returns (bytes memory, PreparedSetupData memory) {
        (_dao, _fromBuild, _payload);
        revert NonUpgradeablePlugin();
    }

    /// @notice Checks if this or the parent contract supports an interface by its ID.
    /// @param _interfaceId The ID of the interface.
    /// @return Returns `true` if the interface is supported.
    function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) {
        return
            _interfaceId == type(IPluginSetup).interfaceId ||
            _interfaceId == type(IProtocolVersion).interfaceId ||
            super.supportsInterface(_interfaceId);
    }

    /// @inheritdoc IPluginSetup
    function implementation() public view returns (address) {
        return IMPLEMENTATION;
    }
}
"
    },
    "lib/ve-governance/lib/osx-commons/contracts/src/plugin/setup/IPluginSetup.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

import {PermissionLib} from "../../permission/PermissionLib.sol";

// solhint-disable-next-line no-unused-import
import {IDAO} from "../../dao/IDAO.sol";

/// @title IPluginSetup
/// @author Aragon X - 2022-2023
/// @notice The interface required for a plugin setup contract to be consumed by the `PluginSetupProcessor` for plugin installations, updates, and uninstallations.
/// @custom:security-contact sirt@aragon.org
interface IPluginSetup {
    /// @notice The data associated with a prepared setup.
    /// @param helpers The address array of helpers (contracts or EOAs) associated with this plugin version after the installation or update.
    /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the installing or updating DAO.
    struct PreparedSetupData {
        address[] helpers;
        PermissionLib.MultiTargetPermission[] permissions;
    }

    /// @notice The payload for plugin updates and uninstallations containing the existing contracts as well as optional data to be consumed by the plugin setup.
    /// @param plugin The address of the `Plugin`.
    /// @param currentHelpers The address array of all current helpers (contracts or EOAs) associated with the plugin to update from.
    /// @param data The bytes-encoded data containing the input parameters for the preparation of update/uninstall as specified in the corresponding ABI on the version's metadata.
    struct SetupPayload {
        address plugin;
        address[] currentHelpers;
        bytes data;
    }

    /// @notice Prepares the installation of a plugin.
    /// @param _dao The address of the installing DAO.
    /// @param _data The bytes-encoded data containing the input parameters for the installation as specified in the plugin's build metadata JSON file.
    /// @return plugin The address of the `Plugin` contract being prepared for installation.
    /// @return preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions.
    function prepareInstallation(
        address _dao,
        bytes calldata _data
    ) external returns (address plugin, PreparedSetupData memory preparedSetupData);

    /// @notice Prepares the update of a plugin.
    /// @param _dao The address of the updating DAO.
    /// @param _fromBuild The build number of the plugin to update from.
    /// @param _payload The relevant data necessary for the `prepareUpdate`. See above.
    /// @return initData The initialization data to be passed to upgradeable contracts when the update is applied in the `PluginSetupProcessor`.
    /// @return preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions.
    function prepareUpdate(
        address _dao,
        uint16 _fromBuild,
        SetupPayload calldata _payload
    ) external returns (bytes memory initData, PreparedSetupData memory preparedSetupData);

    /// @notice Prepares the uninstallation of a plugin.
    /// @param _dao The address of the uninstalling DAO.
    /// @param _payload The relevant data necessary for the `prepareUninstallation`. See above.
    /// @return permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the uninstalling DAO.
    function prepareUninstallation(
        address _dao,
        SetupPayload calldata _payload
    ) external returns (PermissionLib.MultiTargetPermission[] memory permissions);

    /// @notice Returns the plugin implementation address.
    /// @return The address of the plugin implementation contract.
    /// @dev The implementation can be instantiated via the `new` keyword, cloned via the minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)), or proxied via the UUPS proxy pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)).
    function implementation() external view returns (address);
}
"
    },
    "lib/ve-governance/lib/osx-commons/contracts/src/permission/PermissionLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

/// @title PermissionLib
/// @author Aragon X - 2021-2023
/// @notice A library containing objects for permission processing.
/// @custom:security-contact sirt@aragon.org
library PermissionLib {
    /// @notice A constant expressing that no condition is applied to a permission.
    address public constant NO_CONDITION = address(0);

    /// @notice The types of permission operations available in the `PermissionManager`.
    /// @param Grant The grant operation setting a permission without a condition.
    /// @param Revoke The revoke operation removing a permission (that was granted with or without a condition).
    /// @param GrantWithCondition The grant operation setting a permission with a condition.
    enum Operation {
        Grant,
        Revoke,
        GrantWithCondition
    }

    /// @notice A struct containing the information for a permission to be applied on a single target contract without a condition.
    /// @param operation The permission operation type.
    /// @param who The address (EOA or contract) receiving the permission.
    /// @param permissionId The permission identifier.
    struct SingleTargetPermission {
        Operation operation;
        address who;
        bytes32 permissionId;
    }

    /// @notice A struct containing the information for a permission to be applied on multiple target contracts, optionally, with a condition.
    /// @param operation The permission operation type.
    /// @param where The address of the target contract for which `who` receives permission.
    /// @param who The address (EOA or contract) receiving the permission.
    /// @param condition The `PermissionCondition` that will be asked for authorization on calls connected to the specified permission identifier.
    /// @param permissionId The permission identifier.
    struct MultiTargetPermission {
        Operation operation;
        address where;
        address who;
        address condition;
        bytes32 permissionId;
    }
}
"
    },
    "lib/ve-governance/lib/osx-commons/contracts/src/utils/deployment/ProxyLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

/// @title ProxyLib
/// @author Aragon X - 2024
/// @notice A library containing methods for the deployment of proxies via the UUPS pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) and minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)).
/// @custom:security-contact sirt@aragon.org
library ProxyLib {
    using Address for address;
    using Clones for address;

    /// @notice Creates an [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) UUPS proxy contract pointing to a logic contract and allows to immediately initialize it.
    /// @param _logic The logic contract the proxy is pointing to.
    /// @param _initCalldata The initialization data for this contract.
    /// @return uupsProxy The address of the UUPS proxy contract created.
    /// @dev If `_initCalldata` is non-empty, it is used in a delegate call to the `_logic` contract. This will typically be an encoded function call initializing the storage of the proxy (see [OpenZeppelin ERC1967Proxy-constructor](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy-constructor-address-bytes-)).
    function deployUUPSProxy(
        address _logic,
        bytes memory _initCalldata
    ) internal returns (address uupsProxy) {
        uupsProxy = address(new ERC1967Proxy({_logic: _logic, _data: _initCalldata}));
    }

    /// @notice Creates an [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy contract, also known as clones, pointing to a logic contract and allows to immediately initialize it.
    /// @param _logic The logic contract the proxy is pointing to.
    /// @param _initCalldata The initialization data for this contract.
    /// @return minimalProxy The address of the minimal proxy contract created.
    /// @dev If `_initCalldata` is non-empty, it is used in a call to the clone contract. This will typically be an encoded function call initializing the storage of the contract.
    function deployMinimalProxy(
        address _logic,
        bytes memory _initCalldata
    ) internal returns (address minimalProxy) {
        minimalProxy = _logic.clone();
        if (_initCalldata.length > 0) {
            minimalProxy.functionCall({data: _initCalldata});
        }
    }
}
"
    },
    "lib/ve-governance/src/escrow/VotingEscrowIncreasing_v1_2_0.sol": {
      "content": "/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// token interfaces
import {
    IERC20Upgradeable as IERC20
} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {
    IERC20MetadataUpgradeable as IERC20Metadata
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import {IERC721EnumerableMintableBurnable as IERC721EMB} from "@lock/IERC721EMB.sol";

// veGovernance
import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol";
import {IAddressGaugeVoter} from "@voting/IAddressGaugeVoter.sol";
import {
    IEscrowCurveIncreasingV1_2_0 as IEscrowCurve
} from "@curve/IEscrowCurveIncreasing_v1_2_0.sol";
import {IExitQueue} from "@queue/IExitQueue.sol";
import {
    IVotingEscrowIncreasingV1_2_0 as IVotingEscrow,
    IVotingEscrowExiting,
    IMerge,
    ISplit,
    IDelegateMoveVoteCaller
} from "@escrow/IVotingEscrowIncreasing_v1_2_0.sol";
import {IClockV1_2_0 as IClock} from "@clock/IClock_v1_2_0.sol";

// libraries
import {
    SafeERC20Upgradeable as SafeERC20
} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {
    SafeCastUpgradeable as SafeCast
} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";

// parents
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {
    ReentrancyGuardUpgradeable as ReentrancyGuard
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {
    PausableUpgradeable as Pausable
} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {
    DaoAuthorizableUpgradeable as DaoAuthorizable
} from "@aragon/osx-commons-contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol";
import {
    IDelegateUpdateVotingPower,
    IEscrowIVotesAdapter,
    IDelegateMoveVoteRecipient
} from "../delegation/IEscrowIVotesAdapter.sol";

contract VotingEscrowV1_2_0 is
    IVotingEscrow,
    ReentrancyGuard,
    Pausable,
    DaoAuthorizable,
    UUPSUpgradeable
{
    using SafeERC20 for IERC20;
    using SafeCast for uint256;

    /// @notice Role required to manage the Escrow curve, this typically will be the DAO
    bytes32 public constant ESCROW_ADMIN_ROLE = keccak256("ESCROW_ADMIN");

    /// @notice Role required to pause the contract - can be given to emergency contracts
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER");

    /// @notice Role required to withdraw underlying tokens from the contract
    bytes32 public constant SWEEPER_ROLE = keccak256("SWEEPER");

    /// @dev enables splits without whitelisting
    address public constant SPLIT_WHITELIST_ANY_ADDRESS =
        address(uint160(uint256(keccak256("SPLIT_WHITELIST_ANY_ADDRESS"))));

    /*//////////////////////////////////////////////////////////////
                              NFT Data
    //////////////////////////////////////////////////////////////*/

    /// @notice Decimals of the voting power
    uint8 public constant decimals = 18;

    /// @notice Minimum deposit amount
    uint256 public minDeposit;

    /// @notice Auto-incrementing ID for the most recently created lock, does not decrease on withdrawal
    uint256 public lastLockId;

    /// @notice Total supply of underlying tokens deposited in the contract
    uint256 public totalLocked;

    /// @dev tracks the locked balance of each NFT
    mapping(uint256 => LockedBalance) private _locked;

    /*//////////////////////////////////////////////////////////////
                              Helper Contracts
    //////////////////////////////////////////////////////////////*/

    /// @notice Address of the underying ERC20 token.
    /// @dev Only tokens with 18 decimals and no transfer fees are supported
    address public token;

    /// @notice Address of the gauge voting contract.
    /// @dev We need to ensure votes are not left in this contract before allowing positing changes
    address public voter;

    /// @notice Address of the voting Escrow Curve contract that will calculate the voting power
    address public curve;

    /// @notice Address of the contract that manages exit queue logic for withdrawals
    address public queue;

    /// @notice Address of the clock contract that manages epoch and voting periods
    address public clock;

    /// @notice Address of the NFT contract that is the lock
    address public lockNFT;

    bool private _lockNFTSet;

    /*//////////////////////////////////////////////////////////////
                            ADDED: in 1.2.0
    //////////////////////////////////////////////////////////////*/

    /// @notice Whitelisted contracts that are allowed to split
    mapping(address => bool) public splitWhitelisted;

    /// @notice Prevent withdrawals in same creation block
    mapping(uint256 => uint256) internal withdrawalLock;

    /// @notice Addess of the escrow ivotes adapter where delegations occur.
    address public ivotesAdapter;

    /*//////////////////////////////////////////////////////////////
                              Initialization
    //////////////////////////////////////////////////////////////*/

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(
        address _token,
        address _dao,
        address _clock,
        uint256 _initialMinDeposit
    ) external initializer {
        __ReentrancyGuard_init();
        __Pausable_init();
        __DaoAuthorizableUpgradeable_init(IDAO(_dao));

        // if (IERC20Metadata(_token).decimals() != 18) revert MustBe18Decimals();
        token = _token;
        clock = _clock;
        minDeposit = _initialMinDeposit;
        emit MinDepositSet(_initialMinDeposit);
    }

    /// @notice Used to revert if admin tries to change the contract address 2nd time.
    modifier contractAlreadySet(address _contract) {
        if (_contract != address(0)) revert AddressAlreadySet();

        _;
    }

    /*//////////////////////////////////////////////////////////////
                              Admin Setters
    //////////////////////////////////////////////////////////////*/

    /// @notice Added in 1.2.0 to set the ivotes adapter
    function setIVotesAdapter(
        address _ivotesAdapter
    ) external auth(ESCROW_ADMIN_ROLE) contractAlreadySet(ivotesAdapter) {
        ivotesAdapter = _ivotesAdapter;
    }

    /// @notice Sets the curve contract that calculates the voting power
    function setCurve(address _curve) external auth(ESCROW_ADMIN_ROLE) contractAlreadySet(curve) {
        curve = _curve;
    }

    /// @notice Sets the voter contract that tracks votes
    function setVoter(address _voter) external auth(ESCROW_ADMIN_ROLE) {
        voter = _voter;
    }

    /// @notice Sets the exit queue contract that manages withdrawal eligibility
    function setQueue(address _queue) external auth(ESCROW_ADMIN_ROLE) contractAlreadySet(queue) {
        queue = _queue;
    }

    /// @notice Sets the clock contract that manages epoch and voting periods
    function setClock(address _clock) external auth(ESCROW_ADMIN_ROLE) contractAlreadySet(clock) {
        clock = _clock;
    }

    /// @notice Sets the NFT contract that is the lock
    /// @dev By default this can only be set once due to the high risk of changing the lock
    /// and having the ability to steal user funds.
    function setLockNFT(address _nft) external auth(ESCROW_ADMIN_ROLE) {
        if (_lockNFTSet) revert LockNFTAlreadySet();
        lockNFT = _nft;
        _lockNFTSet = true;
    }

    function pause() external auth(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external auth(PAUSER_ROLE) {
        _unpause();
    }

    function setMinDeposit(uint256 _minDeposit) external auth(ESCROW_ADMIN_ROLE) {
        minDeposit = _minDeposit;
        emit MinDepositSet(_minDeposit);
    }

    /// @notice Split disabled by default, only whitelisted addresses can split.
    function setEnableSplit(
        address _account,
        bool _isWhitelisted
    ) external auth(ESCROW_ADMIN_ROLE) {
        splitWhitelisted[_account] = _isWhitelisted;
        emit SplitWhitelistSet(_account, _isWhitelisted);
    }

    /// @notice Enable split to any address without whitelisting
    function enableSplit() external auth(ESCROW_ADMIN_ROLE) {
        splitWhitelisted[SPLIT_WHITELIST_ANY_ADDRESS] = true;
        emit SplitWhitelistSet(SPLIT_WHITELIST_ANY_ADDRESS, true);
    }

    /// @notice Return true if any address is whitelisted or an `_account`.
    function canSplit(address _account) public view virtual returns (bool) {
        // Only allow split to whitelisted accounts.
        return splitWhitelisted[SPLIT_WHITELIST_ANY_ADDRESS] || splitWhitelisted[_account];
    }

    /*//////////////////////////////////////////////////////////////
                      Getters: ERC721 Functions
    //////////////////////////////////////////////////////////////*/

    function isApprovedOrOwner(address _spender, uint256 _tokenId) public view returns (bool) {
        return IERC721EMB(lockNFT).isApprovedOrOwner(_spender, _tokenId);
    }

    /// @notice Fetch all NFTs owned by an address by leveraging the ERC721Enumerable interface
    /// @param _owner Address to query
    /// @return tokenIds Array of token IDs owned by the address
    function ownedTokens(address _owner) public view returns (uint256[] memory tokenIds) {
        IERC721EMB enumerable = IERC721EMB(lockNFT);
        uint256 balance = enumerable.balanceOf(_owner);
        uint256[] memory tokens = new uint256[](balance);
        for (uint256 i = 0; i < balance; i++) {
            tokens[i] = enumerable.tokenOfOwnerByIndex(_owner, i);
        }
        return tokens;
    }

    /*///////////////////////////////////////////////////////////////
                          Getters: Voting
    //////////////////////////////////////////////////////////////*/

    /// @return The voting power of the NFT at the current block
    function votingPower(uint256 _tokenId) public view returns (uint256) {
        return votingPowerAt(_tokenId, block.timestamp);
    }

    /// @return The voting power of the NFT at a specific timestamp
    function votingPowerAt(uint256 _tokenId, uint256 _t) public view returns (uint256) {
        return IEscrowCurve(curve).votingPowerAt(_tokenId, _t);
    }

    /// @return The total voting power at the current block
    function totalVotingPower() external view returns (uint256) {
        return totalVotingPowerAt(block.timestamp);
    }

    /// @return The total voting power at a specific timestamp
    function totalVotingPowerAt(uint256 _timestamp) public view returns (uint256) {
        return IEscrowCurve(curve).supplyAt(_timestamp);
    }

    /// @return The details of the underlying lock for a given veNFT
    function locked(uint256 _tokenId) public view returns (LockedBalance memory) {
        return _locked[_tokenId];
    }

    /// @return accountVotingPower The voting power of an account at the current block
    /// @dev We cannot do historic voting power at this time because we don't current track
    /// histories of token transfers.
    function votingPowerForAccount(
        address _account
    ) external view returns (uint256 accountVotingPower) {
        uint256[] memory tokens = ownedTokens(_account);

        for (uint256 i = 0; i < tokens.length; i++) {
            accountVotingPower += votingPowerAt(tokens[i], block.timestamp);
        }
    }

    /// @notice Checks if the NFT is currently voting. We require the user to reset their votes if so.
    function isVoting(uint256 _tokenId) public view returns (bool) {
        // If token doesn't exist, it reverts.
        address owner = IERC721EMB(lockNFT).ownerOf(_tokenId);

        // If token is not delegated, delegatee wouldn't exist, so we return false.
        bool isTokenDelegated = IEscrowIVotesAdapter(ivotesAdapter).tokenIsDelegated(_tokenId);
        if (!isTokenDelegated) return false;

        // If token is delegated, it will always have a delegatee.
        address delegatee = IEscrowIVotesAdapter(ivotesAdapter).delegates(owner);

        return IAddressGaugeVoter(voter).isVoting(delegatee);
    }

    /*//////////////////////////////////////////////////////////////
                              ESCROW LOGIC
    //////////////////////////////////////////////////////////////*/

    function createLock(uint256 _value) external nonReentrant whenNotPaused returns (uint256) {
        return _createLockFor(_value, _msgSender());
    }

    /// @notice Creates a lock on behalf of someone else.
    function createLockFor(
        uint256 _value,
        address _to
    ) external nonReentrant whenNotPaused returns (uint256) {
        return _createLockFor(_value, _to);
    }

    /// @dev Deposit `_value` tokens for `_to` starting at next deposit interval
    /// @param _value Amount to deposit
    /// @param _to Address to deposit
    function _createLockFor(uint256 _value, address _to) internal returns (uint256) {
        if (_value == 0) revert ZeroAmount();
        if (_value < minDeposit) revert AmountTooSmall();

        // query the duration lib to get the next time we can deposit
        uint256 startTime = IClock(clock).epochPrevCheckpointTs();

        // increment the total locked supply and get the new tokenId
        totalLocked += _value;
        uint256 newTokenId = ++lastLockId;

        // Record the block timestamp for the new tokenId to prevent withdrawals in the same block.
        withdrawalLock[newTokenId] = block.timestamp;

        // write the lock and checkpoint the voting power
        LockedBalance memory lock = LockedBalance(_value.toUint208(), startTime.toUint48());
        _locked[newTokenId] = lock;

        // we don't allow edits in this implementation, so only the new lock is used
        _checkpoint(newTokenId, LockedBalance(0, 0), lock);

        uint256 balanceBefore = IERC20(token).balanceOf(address(this));

        // transfer the tokens into the contract
        IERC20(token).safeTransferFrom(_msgSender(), address(this), _value);

        // we currently don't support tokens that adjust balances on transfer
        if (IERC20(token).balanceOf(address(this)) != balanceBefore + _value)
            revert TransferBalanceIncorrect();

        // Update `_to`'s delegate power.
        _moveDelegateVotes(address(0), _to, newTokenId, lock);

        // mint the NFT before and emit the event to complete the lock
        IERC721EMB(lockNFT).mint(_to, newTokenId);

        emit Deposit(_to, newTokenId, startTime, _value, totalLocked);

        return newTokenId;
    }

    /// @inheritdoc IMerge
    function merge(uint256 _from, uint256 _to) public whenNotPaused {
        address sender = _msgSender();

        if (_from == _to) revert SameNFT();

        address ownerFrom = IERC721EMB(lockNFT).ownerOf(_from);
        address ownerTo = IERC721EMB(lockNFT).ownerOf(_to);

        // Both nfts must have the same owner.
        if (ownerFrom != ownerTo) revert NotSameOwner();

        // sender can either be approved or owner.
        if (!isApprovedOrOwner(sender, _from) || !isApprovedOrOwner(sender, _to)) {
            revert NotApprovedOrOwner();
        }

        LockedBalance memory oldLockedFrom = _locked[_from];
        LockedBalance memory oldLockedTo = _locked[_to];

        if (!canMerge(oldLockedFrom, oldLockedTo)) {
            revert CannotMerge(_from, _to);
        }

        // If `_from` was created in this block, or if another token was merged into `_from` in this block,
        // record the current timestamp for `_to` so that withdrawals for it are blocked in the same block.
        if (withdrawalLock[_from] == block.timestamp) {
            withdrawalLock[_to] = block.timestamp;
        }

        // We only allow merge when both tokens have the same owner.
        // After the merge, owner still should have the same voting power
        // as one token gets merged into another. For this reason,
        // We call `_moveDelegateVotes` with empty locked, so it doesn't
        // reduce/increase the same voting power for gas efficiency.
        IEscrowIVotesAdapter(ivotesAdapter).mergeDelegateVotes(
            IDelegateMoveVoteRecipient.TokenLock(ownerFrom, _from, oldLockedFrom),
            IDelegateMoveVoteRecipient.TokenLock(ownerFrom, _to, oldLockedTo)
        );

        // Update for `_from`.
        // Note that on the checkpoint, we still don't
        // remove `start` for historical reasons.
        IERC721EMB(lockNFT).burn(_from);
        _locked[_from] = LockedBalance(0, 0);
        _checkpoint(_from, oldLockedFrom, LockedBalance(0, oldLockedFrom.start));

        // update for `_to`.
        uint208 newLockedAmount = oldLockedFrom.amount + oldLockedTo.amount;
        _checkpoint(_to, oldLockedTo, LockedBalance(newLockedAmount, oldLockedTo.start));
        _locked[_to] = LockedBalance(newLockedAmount, oldLockedTo.start);

        emit Merged(sender, _from, _to, oldLockedFrom.amount, oldLockedTo.amount, newLockedAmount);
    }

    /// @inheritdoc IMerge
    function canMerge(
        LockedBalance memory _fromLocked,
        LockedBalance memory _toLocked
    ) public view returns (bool) {
        uint256 maxTime = IEscrowCurve(curve).maxTime();

        uint256 fromLockedEnd = _fromLocked.start + maxTime;
        uint256 toLockedEnd = _toLocked.start + maxTime;

        // Tokens either must have the same start dates or both must be mature.
        if (
            (_toLocked.start != _fromLocked.start) &&
            (toLockedEnd >= block.timestamp || fromLockedEnd >= block.timestamp)
        ) {
            return false;
        }

        return true;
    }

    /// @inheritdoc ISplit
    function split(uint256 _from, uint256 _value) public whenNotPaused returns (uint256) {
        if (_value == 0) revert ZeroAmount();

        address sender = _msgSender();

        // For some erc721, `ownerOf` reverts and for some,
        // it returns address(0). For safety, if it doesn't revert,
        // we also check if it's not address(0).
        address owner = IERC721EMB(lockNFT).ownerOf(_from);
        if (owner == address(0)) revert NoOwner();

        if (!canSplit(owner)) revert SplitNotWhitelisted();

        // Sender must either be approved or the owner.
        if (!isApprovedOrOwner(sender, _from)) revert NotApprovedOrOwner();

        LockedBalance memory locked_ = _locked[_from];
        if (locked_.amount <= _value) revert SplitAmountTooBig();

        // Ensure that amounts of new tokens will be greater than `minDeposit`.
        uint208 amount1 = locked_.amount - _value.toUint208();
        uint208 amount2 = _value.toUint208();
        if (amount1 < minDeposit || amount2 < minDeposit) {
            revert AmountTooSmall();
        }

        // update for `_from`.
        _checkpoint(_from, locked_, LockedBalance(amount1, locked_.start));
        _locked[_from] = LockedBalance(amount1, locked_.start);

        uint256 newTokenId = ++lastLockId;

        // preserve the withdrawal lock for `_from` if it exists.
        if (withdrawalLock[_from] == block.timestamp) {
            withdrawalLock[newTokenId] = block.timestamp;
        }

        // owner gets minted a new tokenId. Since `split` function
        // just splits the same amount into two tokenIds, there's no need
        // to update voting power on ivotesAdapter, as total doesn't change.
        // We still call `_moveDelegateVotes` with zero LockedBalance to
        // make sure we update delegatee's token count due to newtokenId.
        IEscrowIVotesAdapter(ivotesAdapter).splitDelegateVotes(
            IDelegateMoveVoteRecipient.TokenLock(owner, _from, LockedBalance(0, 0)),
            IDelegateMoveVoteRecipient.TokenLock(owner, newTokenId, LockedBalance(0, 0))
        );

        // update for `newTokenId`.
        locked_.amount = amount2;
        _createSplitNFT(owner, newTokenId, locked_);

        emit Split(_from, newTokenId, sender, amount1, amount2);

        return newTokenId;
    }

    /// @notice creates a new token in checkpoint and mint.
    /// @param _to The address to which new token id will be minted
    /// @param _tokenId The id of the token that will be minted.
    /// @param _newLocked New locked amount / start lock time for the new token
    function _createSplitNFT(
        address _to,
        uint256 _tokenId,
        LockedBalance memory _newLocked
    ) private {
        _locked[_tokenId] = _newLocked;
        _checkpoint(_tokenId, LockedBalance(0, 0), _newLocked);
        IERC721EMB(lockNFT).mint(_to, _tokenId);
    }

    /// @notice Record per-user data to checkpoints. Used by VotingEscrow system.
    /// @param _tokenId NFT token ID.
    /// @dev Old locked balance is unused in the increasing case, at least in this implementation.
    /// @param _fromLocked New locked amount / start lock time for the user
    /// @param _newLocked New locked amount / start lock time for the user
    function _checkpoint(
        uint256 _tokenId,
        LockedBalance memory _fromLocked,
        LockedBalance memory _newLocked
    ) private {
        IEscrowCurve(curve).checkpoint(_tokenId, _fromLocked, _newLocked);
    }

    /*//////////////////////////////////////////////////////////////
                        Exit and Withdraw Logic
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IVotingEscrowExiting
    function currentExitingAmount() public view returns (uint256 total) {
        IERC721EMB enumerable = IERC721EMB(lockNFT);
        uint256 balance = enumerable.balanceOf(address(this));

        for (uint256 i = 0; i < balance; i++) {
            uint256 tokenId = enumerable.tokenOfOwnerByIndex(address(this), i);
            total += locked(tokenId).amount;
        }
    }

    /// @notice Resets the votes and begins the withdrawal process for a given tokenId
    /// @dev Convenience function, the user must have authorized this contract to act on their behalf.
    ///      For backwards compatibility, even though `reset` call to gauge voter has been removed,
    ///      we still keep the function with the same name.
    function resetVotesAndBeginWithdrawal(uint256 _tokenId) external whenNotPaused {
        beginWithdrawal(_tokenId);
    }

    /// @notice Enters a tokenId into the withdrawal queue by transferring to this contract and creating a ticket.
    /// @param _tokenId The tokenId to begin withdrawal for. Will be transferred to this contract before burning.
    /// @dev The user must not have active votes in the voter contract.
    function beginWithdrawal(uint256 _tokenId) public nonReentrant whenNotPaused {
        // in the event of an increasing curve, 0 voting power means voting isn't active
        if (votingPower(_tokenId) == 0) revert CannotExit();

        // Safety checks:
        // 1. Prevent creating a lock and starting withdrawal in the same block.
        // 2. Prevent withdrawals if another token created in the same block
        //    was merged into `_tokenId`. Even though `_tokenId` itself was
        //    created in a previous block, the merged portion is "fresh" and
        //    would still be withdrawable without restriction.
        IEscrowCurve.TokenPoint memory point = IEscrowCurve(curve).tokenPointHistory(_tokenId, 1);
        if (block.timestamp == withdrawalLock[_tokenId]) {
            revert CannotWithdrawInSameBlock();
        }

        address owner = IERC721EMB(lockNFT).ownerOf(_tokenId);

        // we can remove the user's voting power as it's no longer locked
        LockedBalance memory locked_ = _locked[_tokenId];
        _checkpoint(_tokenId, locked_, LockedBalance(0, locked_.start));

        // transfer NFT to this and queue the exit
        IERC721EMB(lockNFT).transferFrom(_msgSender(), address(this), _tokenId);
        IExitQueue(queue).queueExit(_tokenId, owner);
    }

    /// @notice Allows cancellation of a pending withdrawal request
    /// @dev The caller must be one that also called `beginWithdrawal`.
    /// @param _tokenId The tokenId to cancel the withdrawal request for.
    function cancelWithdrawalRequest(uint256 _tokenId) public nonReentrant whenNotPaused {
        address owner = IExitQueue(queue).ticketHolder(_tokenId);
        address sender = _msgSender();

        if (owner != sender) {
            revert NotTicketHolder();
        }

        _checkpoint(_tokenId, LockedBalance(0, _locked[_tokenId].start), _locked[_tokenId]);

        IExitQueue(queue).cancelExit(_tokenId);
        IERC721EMB(lockNFT).transferFrom(address(this), sender, _tokenId);
    }

    /// @notice Withdraws tokens from the contract
    function withdraw(uint256 _tokenId) external nonReentrant whenNotPaused {
        address sender = _msgSender();

        // we force the sender to be the ticket holder
        if (!(IExitQueue(queue).ticketHolder(_tokenId) == sender)) revert NotTicketHolder();

        // check that this ticket can exit
        if (!(IExitQueue(queue).canExit(_tokenId))) revert CannotExit();

        LockedBalance memory oldLocked = _locked[_tokenId];
        uint256 value = oldLocked.amount;

        // check for fees to be transferred
        // do this before clearing the lock or it will be incorrect
        uint256 fee = IExitQueue(queue).exit(_tokenId);
        if (fee > 0) {
            IERC20(token).safeTransfer(address(queue), fee);
        }

        // clear out the token data
        _locked[_tokenId] = LockedBalance(0, 0);
        totalLocked -= value;

        // Burn the NFT and transfer the tokens to the user
        IERC721EMB(lockNFT).burn(_tokenId);

        IERC20(token).safeTransfer(sender, value - fee);

        emit Withdraw(sender, _tokenId, value - fee, block.timestamp, totalLocked);
    }

    /// @notice withdraw excess tokens from the contract - possibly by accident
    function sweep() external nonReentrant auth(SWEEPER_ROLE) {
        // if there are extra tokens in the contract
        // balance will be greater than the total locked
        uint balance = IERC20(token).balanceOf(address(this));
        uint excess = balance - totalLocked;

        // if there isn't revert the tx
        if (excess == 0) revert NothingToSweep();

        // if there is, send them to the caller
        IERC20(token).safeTransfer(_msgSender(), excess);
        emit Sweep(_msgSender(), excess);
    }

    /// @notice the sweeper can send NFTs mistakenly sent to the contract to a designated address
    /// @param _tokenId the tokenId to sweep - must be currently in this contract
    /// @param _to the address to send the NFT to - must be a whitelisted address for transfers
    /// @dev Cannot sweep NFTs that are in the exit queue for obvious reasons
    function sweepNFT(uint256 _tokenId, address _to) external nonReentrant auth(SWEEPER_ROLE) {
        // if the token id is not in the contract, revert
        if (IERC721EMB(lockNFT).ownerOf(_tokenId) != address(this)) revert NothingToSweep();

        // if the token id is in the queue, we cannot sweep it
        if (IExitQueue(queue).ticketHolder(_tokenId) != address(0)) revert CannotExit();

        IERC721EMB(lockNFT).transferFrom(address(this), _to, _tokenId);
        emit SweepNFT(_to, _tokenId);
    }

    /*//////////////////////////////////////////////////////////////
                        Moving Delegation Votes Logic
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IDelegateMoveVoteCaller
    function moveDelegateVotes(address _from, address _to, uint256 _tokenId) public whenNotPaused {
        if (msg.sender != lockNFT) revert OnlyLockNFT();
        LockedBalance memory locked_ = _locked[_tokenId];

        _moveDelegateVotes(_from, _to, _tokenId, locked_);
    }

    function _moveDelegateVotes(
        address _from,
        address _to,
        uint256 _tokenId,
        LockedBalance memory _lockedBalance
    ) private {
        IEscrowIVotesAdapter(ivotesAdapter).moveDelegateVotes(_from, _to, _tokenId, _lockedBalance);
    }

    function updateVotingPower(address _from, address _to) public whenNotPaused {
        if (msg.sender != ivotesAdapter) revert OnlyIVotesAdapter();

        IAddressGaugeVoter(voter).updateVotingPower(_from, _to);
    }

    /*///////////////////////////////////////////////////////////////
                            UUPS Upgrade
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the address of the implementation contract in the [proxy storage slot](https://eips.ethereum.org/EIPS/eip-1967) slot the [UUPS proxy](https://eips.ethereum.org/EIPS/eip-1822) is pointing to.
    /// @return The address of the implementation contract.
    function implementation() public view returns (address) {
        return _getImplementation();
    }

    /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)).
    function _authorizeUpgrade(address) internal virtual override auth(ESCROW_ADMIN_ROLE) {}

    /// @dev Reserved storage space to allow for layout changes in the future.
    ///      Please note that the reserved slot number in previous version(39) was set
    ///      incorrectly as 39 instead of 40. Changing it to 40 now would overwrite existing slot values,
    ///      resulting in the loss of state. Therefore, we will continue using 36 in this version.
    ///      For future versions, any new variables should be added by subtracting from 36.
    uint256[36] private __gap;
}
"
    },
    "lib/ve-governance/src/clock/Clock_v1_2_0.sol": {
      "content": "/// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// interfaces
import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol";
import {IClock} from "./IClock.sol";
import {IClockV1_2_0} from "./IClock_v1_2_0.sol";

// contracts
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {
    DaoAuthorizableUpgradeable as DaoAuthorizable
} from "@aragon/osx-commons-contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol";

/// @title Clock
contract ClockV1_2_0 is IClockV1_2_0, DaoAuthorizable, UUPSUpgradeable {
    bytes32 public constant CLOCK_ADMIN_ROLE = keccak256("CLOCK_ADMIN_ROLE");

    /// @dev Epoch encompasses a voting and non-voting period
    uint256 internal constant EPOCH_DURATION = 2 weeks;

    /// @dev Checkpoint interval is the time between each voting checkpoi

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Pausable, Non-Fungible, Swap, Voting, Upgradeable, Multi-Signature, Factory|addr:0x6f9c9a6a406819696f56b5c87f37f196e8714a26|verified:true|block:23749278|tx:0x60c07a946ba77b964b00825e47b4a1a02a9a7d4417e04ec614c201006e3406b1|first_check:1762545179

Submitted on: 2025-11-07 20:53:01

Comments

Log in to comment.

No comments yet.