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
Submitted on: 2025-11-07 20:53:01
Comments
Log in to comment.
No comments yet.