Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
V3VoteScript
Compiler Version
v0.8.25+commit.b61c2a91
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.25;
import {IAccessControl} from "@openzeppelin/contracts-v5.2/access/IAccessControl.sol";
import {IBurner} from "contracts/common/interfaces/IBurner.sol";
import {IOssifiableProxy} from "contracts/common/interfaces/IOssifiableProxy.sol";
import {OmnibusBase} from "./utils/OmnibusBase.sol";
import {V3Template} from "./V3Template.sol";
import {OperatorGrid} from "contracts/0.8.25/vaults/OperatorGrid.sol";
interface ITimeConstraints {
function checkTimeAfterTimestampAndEmit(uint40 timestamp) external;
function checkTimeBeforeTimestampAndEmit(uint40 timestamp) external;
function checkTimeWithinDayTimeAndEmit(uint32 startDayTime, uint32 endDayTime) external;
}
interface IEasyTrack {
function addEVMScriptFactory(address _evmScriptFactory, bytes memory _permissions) external;
}
interface IKernel {
function setApp(bytes32 _namespace, bytes32 _appId, address _app) external;
function APP_BASES_NAMESPACE() external view returns (bytes32);
}
interface IOracleDaemonConfig {
function CONFIG_MANAGER_ROLE() external view returns (bytes32);
function set(string calldata _key, bytes calldata _value) external;
}
interface IStakingRouter {
function REPORT_REWARDS_MINTED_ROLE() external view returns (bytes32);
}
interface IVaultsAdapter {
function setVaultJailStatus(address _vault, bool _isInJail) external;
function updateVaultFees(address _vault, uint256 _infrastructureFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP) external;
function forceValidatorExit(address _vault, bytes calldata _pubkeys) external payable;
function socializeBadDebt(address _debtVault, address _acceptorVault, uint256 _shares) external;
}
/// @title V3VoteScript
/// @notice Script for upgrading Lido protocol components
contract V3VoteScript is OmnibusBase {
struct ScriptParams {
address upgradeTemplate;
address timeConstraints;
uint256 odcSlashingReserveWeRightShiftEpochs;
uint256 odcSlashingReserveWeLeftShiftEpochs;
}
//
// Execution window
//
uint32 public constant ENABLED_DAY_SPAN_START = 50400; // 14:00
uint32 public constant ENABLED_DAY_SPAN_END = 82800; // 23:00
//
// Constants
//
uint256 public constant DG_ITEMS_COUNT = 18;
uint256 public constant VOTING_ITEMS_COUNT = 8;
//
// Immutables
//
V3Template public immutable TEMPLATE;
//
// Structured storage
//
ScriptParams public params;
constructor(
ScriptParams memory _params
) OmnibusBase(V3Template(_params.upgradeTemplate).VOTING(), V3Template(_params.upgradeTemplate).DUAL_GOVERNANCE()) {
TEMPLATE = V3Template(_params.upgradeTemplate);
params = _params;
}
function getVotingVoteItems() public view override returns (VoteItem[] memory votingVoteItems) {
votingVoteItems = new VoteItem[](VOTING_ITEMS_COUNT);
address easyTrack = TEMPLATE.EASY_TRACK();
address operatorGrid = TEMPLATE.OPERATOR_GRID();
address vaultsAdapter = TEMPLATE.VAULTS_ADAPTER();
uint256 index = 0;
votingVoteItems[index++] = VoteItem({
description: "2. Add AlterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid, alterTiers)", // 1 is reserved for DG submission item
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_ALTER_TIERS_IN_OPERATOR_GRID(),
bytes.concat(
bytes20(operatorGrid),
bytes4(OperatorGrid.alterTiers.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "3. Add RegisterGroupsInOperatorGrid factory to Easy Track (permissions: operatorGrid, registerGroup + registerTiers)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_REGISTER_GROUPS_IN_OPERATOR_GRID(),
bytes.concat(
bytes20(operatorGrid),
bytes4(OperatorGrid.registerGroup.selector),
bytes20(operatorGrid),
bytes4(OperatorGrid.registerTiers.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "4. Add RegisterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid, registerTiers)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_REGISTER_TIERS_IN_OPERATOR_GRID(),
bytes.concat(
bytes20(operatorGrid),
bytes4(OperatorGrid.registerTiers.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "5. Add UpdateGroupsShareLimitInOperatorGrid factory to Easy Track (permissions: operatorGrid, updateGroupShareLimit)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID(),
bytes.concat(
bytes20(operatorGrid),
bytes4(OperatorGrid.updateGroupShareLimit.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "6. Add SetJailStatusInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, setVaultJailStatus)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_SET_JAIL_STATUS_IN_OPERATOR_GRID(),
bytes.concat(
bytes20(vaultsAdapter),
bytes4(IVaultsAdapter.setVaultJailStatus.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "7. Add UpdateVaultsFeesInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, updateVaultFees)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_UPDATE_VAULTS_FEES_IN_OPERATOR_GRID(),
bytes.concat(
bytes20(vaultsAdapter),
bytes4(IVaultsAdapter.updateVaultFees.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "8. Add ForceValidatorExitsInVaultHub factory to Easy Track (permissions: vaultsAdapter, forceValidatorExit)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_FORCE_VALIDATOR_EXITS_IN_VAULT_HUB(),
bytes.concat(
bytes20(vaultsAdapter),
bytes4(IVaultsAdapter.forceValidatorExit.selector)
)
))
})
});
votingVoteItems[index++] = VoteItem({
description: "9. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter, socializeBadDebt)",
call: ScriptCall({
to: easyTrack,
data: abi.encodeCall(IEasyTrack.addEVMScriptFactory, (
TEMPLATE.ETF_SOCIALIZE_BAD_DEBT_IN_VAULT_HUB(),
bytes.concat(
bytes20(vaultsAdapter),
bytes4(IVaultsAdapter.socializeBadDebt.selector)
)
))
})
});
assert(index == VOTING_ITEMS_COUNT);
}
function getVoteItems() public view override returns (VoteItem[] memory voteItems) {
voteItems = new VoteItem[](DG_ITEMS_COUNT);
uint256 index = 0;
voteItems[index++] = VoteItem({
description: "1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC)",
call: ScriptCall({
to: params.timeConstraints,
data: abi.encodeCall(
ITimeConstraints.checkTimeWithinDayTimeAndEmit,
(
ENABLED_DAY_SPAN_START,
ENABLED_DAY_SPAN_END
)
)
})
});
voteItems[index++] = VoteItem({
description: "1.2. Call V3Template.startUpgrade",
call: _forwardCall(TEMPLATE.AGENT(), params.upgradeTemplate, abi.encodeCall(V3Template.startUpgrade, ()))
});
voteItems[index++] = VoteItem({
description: "1.3. Upgrade LidoLocator implementation",
call: _forwardCall(TEMPLATE.AGENT(), TEMPLATE.LOCATOR(), abi.encodeCall(IOssifiableProxy.proxy__upgradeTo, (TEMPLATE.NEW_LOCATOR_IMPL())))
});
voteItems[index++] = VoteItem({
description: "1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ACL(),
abi.encodeWithSignature(
"grantPermission(address,address,bytes32)",
TEMPLATE.AGENT(),
TEMPLATE.KERNEL(),
keccak256("APP_MANAGER_ROLE")
)
)
});
voteItems[index++] = VoteItem({
description: "1.5. Set Lido implementation in Kernel",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.KERNEL(),
abi.encodeCall(IKernel.setApp, (IKernel(TEMPLATE.KERNEL()).APP_BASES_NAMESPACE(), TEMPLATE.LIDO_APP_ID(), TEMPLATE.NEW_LIDO_IMPL()))
)
});
voteItems[index++] = VoteItem({
description: "1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ACL(),
abi.encodeWithSignature(
"revokePermission(address,address,bytes32)",
TEMPLATE.AGENT(),
TEMPLATE.KERNEL(),
keccak256("APP_MANAGER_ROLE")
)
)
});
bytes32 requestBurnSharesRole = IBurner(TEMPLATE.OLD_BURNER()).REQUEST_BURN_SHARES_ROLE();
voteItems[index++] = VoteItem({
description: "1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.OLD_BURNER(),
abi.encodeCall(IAccessControl.revokeRole, (requestBurnSharesRole, TEMPLATE.LIDO()))
)
});
voteItems[index++] = VoteItem({
description: "1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.OLD_BURNER(),
abi.encodeCall(IAccessControl.revokeRole, (requestBurnSharesRole, TEMPLATE.NODE_OPERATORS_REGISTRY()))
)
});
voteItems[index++] = VoteItem({
description: "1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.OLD_BURNER(),
abi.encodeCall(IAccessControl.revokeRole, (requestBurnSharesRole, TEMPLATE.SIMPLE_DVT()))
)
});
voteItems[index++] = VoteItem({
description: "1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.OLD_BURNER(),
abi.encodeCall(IAccessControl.revokeRole, (requestBurnSharesRole, TEMPLATE.CSM_ACCOUNTING()))
)
});
voteItems[index++] = VoteItem({
description: "1.11. Upgrade AccountingOracle implementation",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ACCOUNTING_ORACLE(),
abi.encodeCall(IOssifiableProxy.proxy__upgradeTo, (TEMPLATE.NEW_ACCOUNTING_ORACLE_IMPL()))
)
});
bytes32 reportRewardsMintedRole = IStakingRouter(TEMPLATE.STAKING_ROUTER()).REPORT_REWARDS_MINTED_ROLE();
voteItems[index++] = VoteItem({
description: "1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.STAKING_ROUTER(),
abi.encodeCall(IAccessControl.revokeRole, (reportRewardsMintedRole, TEMPLATE.LIDO()))
)
});
voteItems[index++] = VoteItem({
description: "1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.STAKING_ROUTER(),
abi.encodeCall(IAccessControl.grantRole, (reportRewardsMintedRole, TEMPLATE.ACCOUNTING()))
)
});
bytes32 configManagerRole = IOracleDaemonConfig(TEMPLATE.ORACLE_DAEMON_CONFIG()).CONFIG_MANAGER_ROLE();
voteItems[index++] = VoteItem({
description: "1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ORACLE_DAEMON_CONFIG(),
abi.encodeCall(IAccessControl.grantRole, (configManagerRole, TEMPLATE.AGENT()))
)
});
voteItems[index++] = VoteItem({
description: "1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT at OracleDaemonConfig",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ORACLE_DAEMON_CONFIG(),
abi.encodeCall(IOracleDaemonConfig.set, ("SLASHING_RESERVE_WE_RIGHT_SHIFT", abi.encode(params.odcSlashingReserveWeRightShiftEpochs)))
)
});
voteItems[index++] = VoteItem({
description: "1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT at OracleDaemonConfig",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ORACLE_DAEMON_CONFIG(),
abi.encodeCall(IOracleDaemonConfig.set, ("SLASHING_RESERVE_WE_LEFT_SHIFT", abi.encode(params.odcSlashingReserveWeLeftShiftEpochs)))
)
});
voteItems[index++] = VoteItem({
description: "1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent",
call: _forwardCall(
TEMPLATE.AGENT(),
TEMPLATE.ORACLE_DAEMON_CONFIG(),
abi.encodeCall(IAccessControl.revokeRole, (configManagerRole, TEMPLATE.AGENT()))
)
});
voteItems[index++] = VoteItem({
description: "1.18. Call V3Template.finishUpgrade",
call: _forwardCall(TEMPLATE.AGENT(), params.upgradeTemplate, abi.encodeCall(V3Template.finishUpgrade, ()))
});
assert(index == DG_ITEMS_COUNT);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControlEnumerable.sol";
import "./AccessControl.sol";
import "../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view override returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view override returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_roleMembers[role].add(account);
}
/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override {
super._revokeRole(role, account);
_roleMembers[role].remove(account);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
return _values(set._inner);
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol";
import {AccessControl} from "../AccessControl.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
return _roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
bool granted = super._grantRole(role, account);
if (granted) {
_roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
bool revoked = super._revokeRole(role, account);
if (revoked) {
_roleMembers[role].remove(account);
}
return revoked;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "../IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC-165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC-165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.20;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/beacon/BeaconProxy.sol)
pragma solidity ^0.8.22;
import {IBeacon} from "./IBeacon.sol";
import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
/**
* @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
*
* The beacon address can only be set once during construction, and cannot be changed afterwards. It is stored in an
* immutable variable to avoid unnecessary storage reads, and also in the beacon storage slot specified by
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] so that it can be accessed externally.
*
* CAUTION: Since the beacon address can never be changed, you must ensure that you either control the beacon, or trust
* the beacon to not upgrade the implementation maliciously.
*
* IMPORTANT: Do not use the implementation logic to modify the beacon storage slot. Doing so would leave the proxy in
* an inconsistent state where the beacon storage slot does not match the beacon address.
*/
contract BeaconProxy is Proxy {
// An immutable address for the beacon to avoid unnecessary SLOADs before each delegate call.
address private immutable _beacon;
/**
* @dev Initializes the proxy with `beacon`.
*
* If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
* will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity
* constructor.
*
* Requirements:
*
* - `beacon` must be a contract with the interface {IBeacon}.
* - If `data` is empty, `msg.value` must be zero.
*/
constructor(address beacon, bytes memory data) payable {
ERC1967Utils.upgradeBeaconToAndCall(beacon, data);
_beacon = beacon;
}
/**
* @dev Returns the current implementation address of the associated beacon.
*/
function _implementation() internal view virtual override returns (address) {
return IBeacon(_getBeacon()).implementation();
}
/**
* @dev Returns the beacon.
*/
function _getBeacon() internal view virtual returns (address) {
return _beacon;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.20;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/UpgradeableBeacon.sol)
pragma solidity ^0.8.20;
import {IBeacon} from "./IBeacon.sol";
import {Ownable} from "../../access/Ownable.sol";
/**
* @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
* implementation contract, which is where they will delegate all function calls.
*
* An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
*/
contract UpgradeableBeacon is IBeacon, Ownable {
address private _implementation;
/**
* @dev The `implementation` of the beacon is invalid.
*/
error BeaconInvalidImplementation(address implementation);
/**
* @dev Emitted when the implementation returned by the beacon is changed.
*/
event Upgraded(address indexed implementation);
/**
* @dev Sets the address of the initial implementation, and the initial owner who can upgrade the beacon.
*/
constructor(address implementation_, address initialOwner) Ownable(initialOwner) {
_setImplementation(implementation_);
}
/**
* @dev Returns the current implementation address.
*/
function implementation() public view virtual returns (address) {
return _implementation;
}
/**
* @dev Upgrades the beacon to a new implementation.
*
* Emits an {Upgraded} event.
*
* Requirements:
*
* - msg.sender must be the owner of the contract.
* - `newImplementation` must be a contract.
*/
function upgradeTo(address newImplementation) public virtual onlyOwner {
_setImplementation(newImplementation);
}
/**
* @dev Sets the implementation contract address for this beacon
*
* Requirements:
*
* - `newImplementation` must be a contract.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert BeaconInvalidImplementation(newImplementation);
}
_implementation = newImplementation;
emit Upgraded(newImplementation);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.22;
import {IBeacon} from "../beacon/IBeacon.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This library provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
*/
library ERC1967Utils {
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit IERC1967.Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit IERC1967.AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the ERC-1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit IERC1967.BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
pragma solidity ^0.8.20;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback
* function and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/Hashes.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of standard hash functions.
*
* _Available since v5.1._
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: This library supports proof verification for merkle trees built using
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
* leaf inclusion in trees built using non-commutative hashing functions requires
* additional logic that is not supported by this library.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeCast {
/**
* @dev Value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev An uint value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toUint248(uint256 value) internal pure returns (uint248) {
if (value > type(uint248).max) {
revert SafeCastOverflowedUintDowncast(248, value);
}
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toUint240(uint256 value) internal pure returns (uint240) {
if (value > type(uint240).max) {
revert SafeCastOverflowedUintDowncast(240, value);
}
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toUint232(uint256 value) internal pure returns (uint232) {
if (value > type(uint232).max) {
revert SafeCastOverflowedUintDowncast(232, value);
}
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
if (value > type(uint224).max) {
revert SafeCastOverflowedUintDowncast(224, value);
}
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toUint216(uint256 value) internal pure returns (uint216) {
if (value > type(uint216).max) {
revert SafeCastOverflowedUintDowncast(216, value);
}
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toUint208(uint256 value) internal pure returns (uint208) {
if (value > type(uint208).max) {
revert SafeCastOverflowedUintDowncast(208, value);
}
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toUint200(uint256 value) internal pure returns (uint200) {
if (value > type(uint200).max) {
revert SafeCastOverflowedUintDowncast(200, value);
}
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toUint192(uint256 value) internal pure returns (uint192) {
if (value > type(uint192).max) {
revert SafeCastOverflowedUintDowncast(192, value);
}
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toUint184(uint256 value) internal pure returns (uint184) {
if (value > type(uint184).max) {
revert SafeCastOverflowedUintDowncast(184, value);
}
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toUint176(uint256 value) internal pure returns (uint176) {
if (value > type(uint176).max) {
revert SafeCastOverflowedUintDowncast(176, value);
}
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toUint168(uint256 value) internal pure returns (uint168) {
if (value > type(uint168).max) {
revert SafeCastOverflowedUintDowncast(168, value);
}
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toUint160(uint256 value) internal pure returns (uint160) {
if (value > type(uint160).max) {
revert SafeCastOverflowedUintDowncast(160, value);
}
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toUint152(uint256 value) internal pure returns (uint152) {
if (value > type(uint152).max) {
revert SafeCastOverflowedUintDowncast(152, value);
}
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toUint144(uint256 value) internal pure returns (uint144) {
if (value > type(uint144).max) {
revert SafeCastOverflowedUintDowncast(144, value);
}
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toUint136(uint256 value) internal pure returns (uint136) {
if (value > type(uint136).max) {
revert SafeCastOverflowedUintDowncast(136, value);
}
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) {
revert SafeCastOverflowedUintDowncast(128, value);
}
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toUint120(uint256 value) internal pure returns (uint120) {
if (value > type(uint120).max) {
revert SafeCastOverflowedUintDowncast(120, value);
}
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toUint112(uint256 value) internal pure returns (uint112) {
if (value > type(uint112).max) {
revert SafeCastOverflowedUintDowncast(112, value);
}
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toUint104(uint256 value) internal pure returns (uint104) {
if (value > type(uint104).max) {
revert SafeCastOverflowedUintDowncast(104, value);
}
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
if (value > type(uint96).max) {
revert SafeCastOverflowedUintDowncast(96, value);
}
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toUint88(uint256 value) internal pure returns (uint88) {
if (value > type(uint88).max) {
revert SafeCastOverflowedUintDowncast(88, value);
}
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toUint80(uint256 value) internal pure returns (uint80) {
if (value > type(uint80).max) {
revert SafeCastOverflowedUintDowncast(80, value);
}
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toUint72(uint256 value) internal pure returns (uint72) {
if (value > type(uint72).max) {
revert SafeCastOverflowedUintDowncast(72, value);
}
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
if (value > type(uint64).max) {
revert SafeCastOverflowedUintDowncast(64, value);
}
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toUint56(uint256 value) internal pure returns (uint56) {
if (value > type(uint56).max) {
revert SafeCastOverflowedUintDowncast(56, value);
}
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toUint48(uint256 value) internal pure returns (uint48) {
if (value > type(uint48).max) {
revert SafeCastOverflowedUintDowncast(48, value);
}
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toUint40(uint256 value) internal pure returns (uint40) {
if (value > type(uint40).max) {
revert SafeCastOverflowedUintDowncast(40, value);
}
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
if (value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, value);
}
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toUint24(uint256 value) internal pure returns (uint24) {
if (value > type(uint24).max) {
revert SafeCastOverflowedUintDowncast(24, value);
}
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
if (value > type(uint16).max) {
revert SafeCastOverflowedUintDowncast(16, value);
}
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toUint8(uint256 value) internal pure returns (uint8) {
if (value > type(uint8).max) {
revert SafeCastOverflowedUintDowncast(8, value);
}
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(248, value);
}
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(240, value);
}
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(232, value);
}
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(224, value);
}
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(216, value);
}
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(208, value);
}
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(200, value);
}
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(192, value);
}
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(184, value);
}
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(176, value);
}
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(168, value);
}
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(160, value);
}
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(152, value);
}
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(144, value);
}
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(136, value);
}
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(128, value);
}
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(120, value);
}
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(112, value);
}
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(104, value);
}
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(96, value);
}
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(88, value);
}
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(80, value);
}
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(72, value);
}
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(64, value);
}
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(56, value);
}
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(48, value);
}
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(40, value);
}
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(32, value);
}
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(24, value);
}
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(16, value);
}
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(8, value);
}
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
if (value > uint256(type(int256).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int256(value);
}
/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function toUint(bool b) internal pure returns (uint256 u) {
assembly ("memory-safe") {
u := iszero(iszero(b))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
}// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {AccessControlEnumerable} from "@openzeppelin/contracts-v5.2/access/extensions/AccessControlEnumerable.sol"; import {Confirmations} from "./Confirmations.sol"; /** * @title AccessControlConfirmable * @author Lido * @notice An extension of AccessControlEnumerable that allows executing functions by mutual confirmation. * @dev This contract extends Confirmations and AccessControlEnumerable and adds a confirmation mechanism. */ abstract contract AccessControlConfirmable is AccessControlEnumerable, Confirmations { constructor() { __Confirmations_init(); } function _isValidConfirmer(bytes32 _role) internal view override returns (bool) { return hasRole(_role, msg.sender); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {Confirmations} from "./Confirmations.sol"; /** * @title Confirmable2Addresses * @author Lido * @notice An extension of Confirmations that allows executing functions by mutual confirmation. * @dev In this implementation, roles are treated as addresses. */ abstract contract Confirmable2Addresses is Confirmations { function _collectAndCheckConfirmations(bytes calldata _calldata, address _role1, address _role2) internal returns (bool) { bytes32[] memory roles = new bytes32[](2); roles[0] = bytes32(uint256(uint160(_role1))); roles[1] = bytes32(uint256(uint160(_role2))); return _collectAndCheckConfirmations(_calldata, roles); } function _isValidConfirmer(bytes32 _roleAsAddress) internal view override returns (bool) { return _roleAsAddress == bytes32(uint256(uint160(msg.sender))); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; /** * @title Confirmations * @author Lido * @notice A contract that allows executing functions by mutual confirmation. */ abstract contract Confirmations { /** * @notice Tracks confirmations * @custom:storage-location erc7201:Lido.Utils.Confirmations * @dev We cannot set confirmExpiry to 0 because this means that all confirmations have to be in the same block, * which can never be guaranteed. And, more importantly, if the `_setConfirmExpiry` requires confirmation, * the confirmation expiry will be tricky to change. * This is why confirmExpiry is private, set to a default value of 1 days and cannot be set to 0. * * Storage layout: * - callData: msg.data of the call (selector + arguments) * - role: role that confirmed the action * - expiryTimestamp: timestamp of the confirmation * * - confirmExpiry: confirmation expiry period in seconds */ struct ConfirmationStorage { mapping(bytes callData => mapping(bytes32 role => uint256 expiryTimestamp)) confirmations; uint256 confirmExpiry; } /** * @notice Storage offset slot for ERC-7201 namespace * The storage namespace is used to prevent upgrade collisions * keccak256(abi.encode(uint256(keccak256("Lido.Utils.Confirmations")) - 1)) & ~bytes32(uint256(0xff)) */ bytes32 private constant CONFIRMATIONS_STORAGE_LOCATION = 0xe4ca011a1344eb515c922209bf867930fc05bf79f4b0e3bb4ec9938eedd47700; /** * @notice Minimal confirmation expiry in seconds. */ uint256 public constant MIN_CONFIRM_EXPIRY = 1 hours; /** * @notice Maximal confirmation expiry in seconds. */ uint256 public constant MAX_CONFIRM_EXPIRY = 30 days; function __Confirmations_init() internal { _setConfirmExpiry(1 days); } /** * @notice Returns the confirmation expiry. * @return The confirmation expiry in seconds. */ function getConfirmExpiry() public view returns (uint256) { return _getConfirmationsStorage().confirmExpiry; } /** * @notice Returns the confirmation expiry for a given call data and confirmer. * @param _callData The call data of the function. * @param _role The role of the confirmer. * @return The confirmation expiration timestamp or 0 if there was no confirmation from this role to this _callData */ function confirmation(bytes memory _callData, bytes32 _role) external view returns (uint256) { return _getConfirmationsStorage().confirmations[_callData][_role]; } /** * @dev Processes a confirmation from the current caller and checks if all required confirmations are present. * Confirmation, in this context, is a call to the same function with the same arguments. * This is a one-off operation that either: * - Collects the current caller's confirmation and returns false if not enough confirmations * - Or clears all confirmations and returns true if all required confirmations are present * * The confirmation process works as follows: * 1. When a role member calls the function: * - Their confirmation is counted immediately * - If not enough confirmations exist, their confirmation is recorded * - If they're not a member of any of the specified roles, the call reverts * * 2. Confirmation counting: * - Counts the current caller's confirmations if they're a member of any of the specified roles * - Counts existing confirmations that are not expired, i.e. expiry is not exceeded * * 3. Confirmation Management: * - If all members of the specified roles have confirmed: * a. Clears all confirmations for this call * b. Returns true to indicate that the function can be executed * - If not enough confirmations: * a. Stores the current confirmations * b. Returns false to indicate that the function cannot be executed yet * - Thus, if the caller has all the roles, returns true immediately * * 4. Gas Optimization: * - Confirmations are stored in a deferred manner using a memory array * - Confirmation storage writes only occur if the function cannot be executed immediately * - This prevents unnecessary storage writes when all confirmations are present, * because the confirmations are cleared anyway after the function is executed, * - i.e. this optimization is beneficial for the deciding caller and * saves 1 storage write for each role the deciding caller has * * @param _calldata msg.data of the call (selector + arguments) * @param _roles Array of role identifiers that must confirm the call in order to execute it * @return bool True if all required confirmations are present and the function can be executed, false otherwise * * @notice Confirmations past their expiry are not counted and must be recast * @notice Only members of the specified roles can submit confirmations * @notice The order of confirmations does not matter * */ function _collectAndCheckConfirmations(bytes calldata _calldata, bytes32[] memory _roles) internal returns (bool) { if (_roles.length == 0) revert ZeroConfirmingRoles(); uint256 numberOfRoles = _roles.length; uint256 numberOfConfirms = 0; bool[] memory deferredConfirms = new bool[](numberOfRoles); bool isRoleMember = false; ConfirmationStorage storage $ = _getConfirmationsStorage(); uint256 expiryTimestamp = block.timestamp + $.confirmExpiry; for (uint256 i = 0; i < numberOfRoles; ++i) { bytes32 role = _roles[i]; if (_isValidConfirmer(role)) { isRoleMember = true; numberOfConfirms++; deferredConfirms[i] = true; emit RoleMemberConfirmed(msg.sender, role, block.timestamp, expiryTimestamp, msg.data); } else if ($.confirmations[_calldata][role] >= block.timestamp) { numberOfConfirms++; } } if (!isRoleMember) revert SenderNotMember(); if (numberOfConfirms == numberOfRoles) { for (uint256 i = 0; i < numberOfRoles; ++i) { bytes32 role = _roles[i]; delete $.confirmations[_calldata][role]; } return true; } else { for (uint256 i = 0; i < numberOfRoles; ++i) { if (deferredConfirms[i]) { bytes32 role = _roles[i]; $.confirmations[_calldata][role] = expiryTimestamp; } } return false; } } /** * @notice Checks if the caller is a valid confirmer * @param _role The role to check * @return bool True if the caller is a valid confirmer */ function _isValidConfirmer(bytes32 _role) internal view virtual returns (bool); /** * @dev Sets the confirmation expiry. * Confirmation expiry is a period during which the confirmation is counted. Once expired, * the confirmation no longer counts and must be recasted for the confirmation to go through. * @dev Does not retroactively apply to existing confirmations. * @param _newConfirmExpiry The new confirmation expiry in seconds. */ function _setConfirmExpiry(uint256 _newConfirmExpiry) internal { _validateConfirmExpiry(_newConfirmExpiry); ConfirmationStorage storage $ = _getConfirmationsStorage(); uint256 oldConfirmExpiry = $.confirmExpiry; $.confirmExpiry = _newConfirmExpiry; emit ConfirmExpirySet(msg.sender, oldConfirmExpiry, _newConfirmExpiry); } function _validateConfirmExpiry(uint256 _newConfirmExpiry) internal pure { if (_newConfirmExpiry < MIN_CONFIRM_EXPIRY || _newConfirmExpiry > MAX_CONFIRM_EXPIRY) revert ConfirmExpiryOutOfBounds(); } function _getConfirmationsStorage() private pure returns (ConfirmationStorage storage $) { assembly { $.slot := CONFIRMATIONS_STORAGE_LOCATION } } /** * @dev Emitted when the confirmation expiry is set. * @param sender msg.sender of the call * @param oldConfirmExpiry The old confirmation expiry. * @param newConfirmExpiry The new confirmation expiry. */ event ConfirmExpirySet(address indexed sender, uint256 oldConfirmExpiry, uint256 newConfirmExpiry); /** * @dev Emitted when a role member confirms. * @param member The address of the confirming member. * @param roleOrAddress The role or address of the confirming member. * @param confirmTimestamp The timestamp of the confirmation. * @param expiryTimestamp The timestamp when this confirmation expires. * @param data The msg.data of the confirmation (selector + arguments). */ event RoleMemberConfirmed(address indexed member, bytes32 indexed roleOrAddress, uint256 confirmTimestamp, uint256 expiryTimestamp, bytes data); /** * @dev Thrown when attempting to set confirmation expiry out of bounds. */ error ConfirmExpiryOutOfBounds(); /** * @dev Thrown when a caller without a required role attempts to confirm. */ error SenderNotMember(); /** * @dev Thrown when the roles array is empty. */ error ZeroConfirmingRoles(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; /** * @title PausableUntilWithRoles * @notice a `PausableUntil` implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable` * @dev the inheriting contract must use `whenNotPaused` modifier from `PausableUntil` to block some functions on pause */ abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerableUpgradeable { /// @notice role that allows to pause the contract /// @dev 0x8d0e4ae4847b49935b55c99f9c3ce025c87e7c4604c35b7ae56929bd32fa5a78 bytes32 public constant PAUSE_ROLE = keccak256("PausableUntilWithRoles.PauseRole"); /// @notice role that allows to resume the contract /// @dev 0xa79a6aede309e0d48bf2ef0f71355c06ad317956d4c0da2deb0dc47cc34f826c bytes32 public constant RESUME_ROLE = keccak256("PausableUntilWithRoles.ResumeRole"); /** * @notice Resume the contract * @dev Reverts if contracts is not paused * @dev Reverts if sender has no `RESUME_ROLE` */ function resume() external onlyRole(RESUME_ROLE) { _resume(); } /** * @notice Pause the contract for a specified period * @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) * @dev Reverts if contract is already paused * @dev Reverts if sender has no `PAUSE_ROLE` * @dev Reverts if zero duration is passed */ function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { _pauseFor(_duration); } /** * @notice Pause the contract until a specified timestamp * @param _pauseUntilInclusive the last second to pause until inclusive * @dev Reverts if the timestamp is in the past * @dev Reverts if sender has no `PAUSE_ROLE` * @dev Reverts if contract is already paused */ function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) { _pauseUntil(_pauseUntilInclusive); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {SafeERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/IERC20.sol"; import {RecoverTokens} from "../lib/RecoverTokens.sol"; import {ILido as IStETH} from "contracts/common/interfaces/ILido.sol"; import {IDepositContract} from "contracts/common/interfaces/IDepositContract.sol"; import {IStakingVault} from "../interfaces/IStakingVault.sol"; import {IPredepositGuarantee} from "../interfaces/IPredepositGuarantee.sol"; import {NodeOperatorFee} from "./NodeOperatorFee.sol"; import {VaultHub} from "../VaultHub.sol"; interface IWstETH is IERC20 { function wrap(uint256 _stETHAmount) external returns (uint256); function unwrap(uint256 _wstETHAmount) external returns (uint256); } /** * @title Dashboard * @notice This contract is a UX-layer for StakingVault and meant to be used as its owner. * This contract improves the vault UX by bundling all functions from the StakingVault and VaultHub * in this single contract. It provides administrative functions for managing the StakingVault, * including funding, withdrawing, minting, burning, and rebalancing operations. */ contract Dashboard is NodeOperatorFee { /// @dev 0xb694d4d19c77484e8f232470d9bf7e10450638db998b577a833d46df71fb6d97 bytes32 public constant COLLECT_VAULT_ERC20_ROLE = keccak256("vaults.Dashboard.CollectVaultERC20"); /** * @notice The stETH token contract */ IStETH public immutable STETH; /** * @notice The wstETH token contract */ IWstETH public immutable WSTETH; /** * @notice Slot for the fund-on-receive flag * keccak256("vaults.Dashboard.fundOnReceive") */ bytes32 public constant FUND_ON_RECEIVE_FLAG_SLOT = 0x7408b7b034fda7051615c19182918ecb91d753231cffd86f81a45d996d63e038; /** * @notice The PDG policy modes. * "STRICT": deposits require the full PDG process. * "ALLOW_PROVE": allows the node operator to prove unknown validators to PDG. * "ALLOW_DEPOSIT_AND_PROVE": allows the node operator to perform unguaranteed deposits * (bypassing the predeposit requirement) and proving unknown validators. */ enum PDGPolicy { STRICT, ALLOW_PROVE, ALLOW_DEPOSIT_AND_PROVE } /** * @notice Current active PDG policy set by `DEFAULT_ADMIN_ROLE`. */ PDGPolicy public pdgPolicy = PDGPolicy.STRICT; /** * @notice the amount of node operator fees accrued on the moment of disconnection and secured to be recovered to * the `feeRecipient` address using `recoverFeeLeftover` method */ uint128 public feeLeftover; /** * @notice Constructor sets the stETH, and WSTETH token addresses, * and passes the address of the vault hub up the inheritance chain. * @param _stETH Address of the stETH token contract. * @param _wstETH Address of the wstETH token contract. * @param _vaultHub Address of the vault hub contract. * @param _lidoLocator Address of the Lido locator contract. */ constructor( address _stETH, address _wstETH, address _vaultHub, address _lidoLocator ) NodeOperatorFee(_vaultHub, _lidoLocator) { _requireNotZero(_stETH); _requireNotZero(_wstETH); // stETH and wstETH are cached as immutable to save gas for main operations STETH = IStETH(_stETH); WSTETH = IWstETH(_wstETH); } /** * @notice Calls the parent's initializer and approves the max allowance for WSTETH for gas savings * @param _defaultAdmin The address of the default admin * @param _nodeOperatorManager The address of the node operator manager * @param _nodeOperatorFeeRecipient The address of the node operator fee recipient * @param _nodeOperatorFeeBP The node operator fee in basis points * @param _confirmExpiry The confirmation expiry time in seconds */ function initialize( address _defaultAdmin, address _nodeOperatorManager, address _nodeOperatorFeeRecipient, uint256 _nodeOperatorFeeBP, uint256 _confirmExpiry ) external { super._initialize( _defaultAdmin, _nodeOperatorManager, _nodeOperatorFeeRecipient, _nodeOperatorFeeBP, _confirmExpiry ); // reduces gas cost for `mintWsteth` // invariant: dashboard does not hold stETH on its balance STETH.approve(address(WSTETH), type(uint256).max); } // ==================== View Functions ==================== /** * @notice Returns the vault connection data for the staking vault. * @return VaultConnection struct containing vault data */ function vaultConnection() public view returns (VaultHub.VaultConnection memory) { return VAULT_HUB.vaultConnection(address(_stakingVault())); } /** * @notice Returns the number of stETH shares minted */ function liabilityShares() public view returns (uint256) { return VAULT_HUB.liabilityShares(address(_stakingVault())); } /** * @notice Returns the total value of the vault in ether. */ function totalValue() external view returns (uint256) { return VAULT_HUB.totalValue(address(_stakingVault())); } /** * @notice Returns the locked amount of ether for the vault */ function locked() external view returns (uint256) { return VAULT_HUB.locked(address(_stakingVault())); } /** * @notice Returns the amount of shares to burn to restore vault healthiness or to cover redemptions and the * amount of outstanding Lido fees * @return sharesToBurn amount of shares to burn or to rebalance * @return feesToSettle amount of Lido fees to be settled */ function obligations() external view returns (uint256 sharesToBurn, uint256 feesToSettle) { (sharesToBurn, feesToSettle) = VAULT_HUB.obligations(address(_stakingVault())); } /** * @notice Returns the amount of shares to rebalance to restore vault healthiness or to cover redemptions * @dev returns UINT256_MAX if it's impossible to make the vault healthy using rebalance */ function healthShortfallShares() external view returns (uint256) { return VAULT_HUB.healthShortfallShares(address(_stakingVault())); } /** * @notice Returns the amount of ether required to cover obligations shortfall of the vault * @dev returns UINT256_MAX if it's impossible to cover obligations shortfall * @dev NB: obligationsShortfallValue includes healthShortfallShares converted to ether and any unsettled Lido fees * in case they are greater than the minimum beacon deposit */ function obligationsShortfallValue() external view returns (uint256) { return VAULT_HUB.obligationsShortfallValue(address(_stakingVault())); } /** * @notice Returns the amount of ether that is locked on the vault only as a reserve. * @dev There is no way to mint stETH for it (it includes connection deposit and slashing reserve) */ function minimalReserve() public view returns (uint256) { return VAULT_HUB.vaultRecord(address(_stakingVault())).minimalReserve; } /** * @notice Returns the max total lockable amount of ether for the vault (excluding the Lido and node operator fees) */ function maxLockableValue() external view returns (uint256) { uint256 maxLockableValue_ = VAULT_HUB.maxLockableValue(address(_stakingVault())); uint256 nodeOperatorFee = accruedFee(); return maxLockableValue_ > nodeOperatorFee ? maxLockableValue_ - nodeOperatorFee : 0; } /** * @notice Returns the overall capacity for stETH shares that can be minted by the vault */ function totalMintingCapacityShares() external view returns (uint256) { return _totalMintingCapacityShares(-int256(accruedFee())); } /** * @notice Returns the remaining capacity for stETH shares that can be minted * by the vault if additional ether is funded * @param _etherToFund the amount of ether to be funded, can be zero * @return the number of shares that can be minted using additional ether */ function remainingMintingCapacityShares(uint256 _etherToFund) public view returns (uint256) { int256 deltaValue = int256(_etherToFund) - int256(accruedFee()); uint256 vaultTotalMintingCapacityShares = _totalMintingCapacityShares(deltaValue); uint256 vaultLiabilityShares = liabilityShares(); if (vaultTotalMintingCapacityShares <= vaultLiabilityShares) return 0; return vaultTotalMintingCapacityShares - vaultLiabilityShares; } /** * @notice Returns the amount of ether that can be instantly withdrawn from the staking vault. * @dev This is the amount of ether that is not locked in the StakingVault and not reserved for fees and obligations. */ function withdrawableValue() public view returns (uint256) { uint256 withdrawable = VAULT_HUB.withdrawableValue(address(_stakingVault())); uint256 nodeOperatorFee = accruedFee(); return withdrawable > nodeOperatorFee ? withdrawable - nodeOperatorFee : 0; } // ==================== Vault Management Functions ==================== /** * @dev Automatically funds the staking vault with ether */ receive() external payable { if (_shouldFundOnReceive()) _fund(msg.value); } /** * @notice Transfers the ownership of the underlying StakingVault from this contract to a new owner * without disconnecting it from the hub * @param _newOwner Address of the new owner. * @return bool True if the ownership transfer was executed, false if pending for confirmation * @dev after invoking this method node operator fee accrual is effectively disabled * to reenable it (after disconnect fail or reconnect) the related parties must agree on `settledGrowth` * using `correctSettledGrowth()` method */ function transferVaultOwnership(address _newOwner) external returns (bool) { if (_newOwner == address(this)) revert DashboardNotAllowed(); if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; disburseFee(); _stopFeeAccrual(); VAULT_HUB.transferVaultOwnership(address(_stakingVault()), _newOwner); return true; } /** * @notice Initiates the disconnection of the underlying StakingVault from the hub and passing its ownership * to Dashboard contract. Disconnection is finalized by applying the next oracle report for this vault, * after which one can call reconnectToVaultHub() to reconnect the vault * or abandonDashboard() to transfer the ownership further to a new owner. * @dev reverts if there is not enough ether on the vault balance to pay the accrued node operator fees * @dev node operator fees accrued on the moment of disconnection are collected to Dashboard address as `feeLeftover` * and can be recovered later to the fee recipient address * @dev after invoking this method node operator fee accrual is effectively disabled * to reenable it (after disconnect fail or reconnect) the related parties must agree on `settledGrowth` * using `correctSettledGrowth()` method */ function voluntaryDisconnect() external { // fee are not disbursed to the feeRecipient address to avoid reverts blocking the disconnection _collectFeeLeftover(); _stopFeeAccrual(); _voluntaryDisconnect(); } /** * @notice Recovers the previously collected fees to the feeRecipient address */ function recoverFeeLeftover() external { uint256 feeToTransfer = feeLeftover; feeLeftover = 0; RecoverTokens._recoverEth(feeRecipient, feeToTransfer); } /** * @notice Accepts the ownership over the disconnected StakingVault transferred from VaultHub * and immediately passes it to a new pending owner. This new owner will have to accept the ownership * on the StakingVault contract. * @param _newOwner The address to transfer the StakingVault ownership to. */ function abandonDashboard(address _newOwner) external { if (VAULT_HUB.isVaultConnected(address(_stakingVault()))) revert ConnectedToVaultHub(); if (_newOwner == address(this)) revert DashboardNotAllowed(); _acceptOwnership(); _transferOwnership(_newOwner); } /** * @notice Accepts the ownership over the StakingVault and connects to VaultHub. Can be called to reconnect * to the hub after voluntaryDisconnect() * @dev reverts if settledGrowth is not corrected after the vault is disconnected */ function reconnectToVaultHub() external { _acceptOwnership(); connectToVaultHub(); } /** * @notice Connects to VaultHub, transferring underlying StakingVault ownership to VaultHub. * @dev reverts if settledGrowth is not corrected after the vault is disconnected */ function connectToVaultHub() public payable { if (settledGrowth >= MAX_SANE_SETTLED_GROWTH && feeRate != 0) { revert SettleGrowthIsNotSet(); } if (msg.value > 0) _stakingVault().fund{value: msg.value}(); _transferOwnership(address(VAULT_HUB)); VAULT_HUB.connectVault(address(_stakingVault())); } /** * @notice Changes the tier of the vault and connects to VaultHub * @param _tierId The tier to change to * @param _requestedShareLimit The requested share limit * @dev reverts if settledGrowth is not corrected after the vault is disconnected */ function connectAndAcceptTier(uint256 _tierId, uint256 _requestedShareLimit) external payable { connectToVaultHub(); if (!_changeTier(_tierId, _requestedShareLimit)) { revert TierChangeNotConfirmed(); } } /** * @notice Funds the staking vault with ether */ function fund() external payable { _fund(msg.value); } /** * @notice Withdraws ether from the staking vault to a recipient * @param _recipient Address of the recipient * @param _ether Amount of ether to withdraw */ function withdraw(address _recipient, uint256 _ether) external { uint256 withdrawableEther = withdrawableValue(); if (_ether > withdrawableEther) { revert ExceedsWithdrawable(_ether, withdrawableEther); } _withdraw(_recipient, _ether); } /** * @notice Mints stETH shares backed by the vault to the recipient. * @param _recipient Address of the recipient * @param _amountOfShares Amount of stETH shares to mint */ function mintShares(address _recipient, uint256 _amountOfShares) external payable fundable { _mintSharesWithinMintingCapacity(_recipient, _amountOfShares); } /** * @notice Mints stETH tokens backed by the vault to the recipient. * !NB: this will revert with `ZeroArgument()` if the amount of stETH is less than 1 share * @param _recipient Address of the recipient * @param _amountOfStETH Amount of stETH to mint */ function mintStETH(address _recipient, uint256 _amountOfStETH) external payable fundable { _mintSharesWithinMintingCapacity(_recipient, _getSharesByPooledEth(_amountOfStETH)); } /** * @notice Mints wstETH tokens backed by the vault to a recipient. * @param _recipient Address of the recipient * @param _amountOfWstETH Amount of tokens to mint */ function mintWstETH(address _recipient, uint256 _amountOfWstETH) external payable fundable { _mintSharesWithinMintingCapacity(address(this), _amountOfWstETH); uint256 mintedStETH = STETH.getPooledEthBySharesRoundUp(_amountOfWstETH); uint256 wrappedWstETH = WSTETH.wrap(mintedStETH); SafeERC20.safeTransfer(WSTETH, _recipient, wrappedWstETH); } /** * @notice Burns stETH shares from the sender backed by the vault. * Expects corresponding amount of stETH approved to this contract. * @param _amountOfShares Amount of stETH shares to burn */ function burnShares(uint256 _amountOfShares) external { STETH.transferSharesFrom(msg.sender, address(VAULT_HUB), _amountOfShares); _burnShares(_amountOfShares); } /** * @notice Burns stETH tokens from the sender backed by the vault. Expects stETH amount approved to this contract. * !NB: this will revert with `ZeroArgument()` if the amount of stETH is less than 1 share * @param _amountOfStETH Amount of stETH tokens to burn */ function burnStETH(uint256 _amountOfStETH) external { _burnStETH(_amountOfStETH); } /** * @notice Burns wstETH tokens from the sender backed by the vault. Expects wstETH amount approved to this contract. * @dev !NB: this will revert with `ZeroArgument()` on 1 wei of wstETH due to rounding inside wstETH unwrap method * @param _amountOfWstETH Amount of wstETH tokens to burn */ function burnWstETH(uint256 _amountOfWstETH) external { _burnWstETH(_amountOfWstETH); } /** * @notice Rebalances the vault's position by transferring ether corresponding to the passed `_shares` * number to Lido Core and writing it off from the vault's liability. * @param _shares amount of shares to rebalance */ function rebalanceVaultWithShares(uint256 _shares) external { _rebalanceVault(_shares); } /** * @notice Rebalances the vault by transferring ether and writing off the respective shares amount fro the vault's * liability * @param _ether amount of ether to rebalance * @dev the amount of ether transferred can differ a bit because of the rounding */ function rebalanceVaultWithEther(uint256 _ether) external payable fundable { _rebalanceVault(_getSharesByPooledEth(_ether)); } /** * @notice Changes the PDG policy. PDGPolicy regulates the possibility of deposits without PredepositGuarantee * @param _pdgPolicy new PDG policy */ function setPDGPolicy(PDGPolicy _pdgPolicy) external onlyRoleMemberOrAdmin(DEFAULT_ADMIN_ROLE) { if (_pdgPolicy == pdgPolicy) revert PDGPolicyAlreadyActive(); pdgPolicy = _pdgPolicy; emit PDGPolicyEnacted(_pdgPolicy); } /** * @notice Withdraws ether from vault and deposits directly to provided validators bypassing the default PDG process, * allowing validators to be proven post-factum via `proveUnknownValidatorsToPDG` clearing them for future * deposits via `PDG.topUpValidators`. Requires the node operator and vault owner have mutual trust. * @param _deposits array of IStakingVault.Deposit structs containing deposit data * @return totalAmount total amount of ether deposited to beacon chain * @dev requires the PDG policy set to `ALLOW_DEPOSIT_AND_PROVE` * @dev requires the caller to have the `NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE` * @dev Warning! vulnerable to deposit frontrunning and requires putting trust on the node operator * @dev Warning! Prevents node operator fee disbursement till the moment the deposited amount is reported as the part * of the vault total value (depends on the length of the Ethereum entrance queue). Fee may never be disbursed * if the vault is disconnected before the deposit arrives. Recommended to disburse all available fees * before depositing via this method. */ function unguaranteedDepositToBeaconChain( IStakingVault.Deposit[] calldata _deposits ) external returns (uint256 totalAmount) { if (pdgPolicy != PDGPolicy.ALLOW_DEPOSIT_AND_PROVE) revert ForbiddenByPDGPolicy(); IStakingVault stakingVault_ = _stakingVault(); IDepositContract depositContract = stakingVault_.DEPOSIT_CONTRACT(); for (uint256 i = 0; i < _deposits.length; i++) { totalAmount += _deposits[i].amount; } uint256 withdrawableEther = withdrawableValue(); if (totalAmount > withdrawableEther) { revert ExceedsWithdrawable(totalAmount, withdrawableEther); } _disableFundOnReceive(); _withdrawForUnguaranteedDepositToBeaconChain(totalAmount); // Instead of relying on auto-reset at the end of the transaction, // re-enable fund-on-receive manually to restore the default receive() behavior in the same transaction _enableFundOnReceive(); _addFeeExemption(totalAmount); bytes memory withdrawalCredentials = bytes.concat(stakingVault_.withdrawalCredentials()); IStakingVault.Deposit calldata deposit; for (uint256 i = 0; i < _deposits.length; i++) { deposit = _deposits[i]; depositContract.deposit{value: deposit.amount}( deposit.pubkey, withdrawalCredentials, deposit.signature, deposit.depositDataRoot ); } emit UnguaranteedDeposits(address(stakingVault_), _deposits.length, totalAmount); } /** * @notice Proves validators with correct vault WC if they are unknown to PDG * @param _witnesses array of IPredepositGuarantee.ValidatorWitness structs containing proof data for validators * @dev requires the PDG policy set to `ALLOW_PROVE` or `ALLOW_DEPOSIT_AND_PROVE` * @dev requires the caller to have the `NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE` */ function proveUnknownValidatorsToPDG(IPredepositGuarantee.ValidatorWitness[] calldata _witnesses) external { if (pdgPolicy == PDGPolicy.STRICT) revert ForbiddenByPDGPolicy(); _proveUnknownValidatorsToPDG(_witnesses); } /** * @notice Recovers ERC20 tokens or ether from the dashboard contract to the recipient * @param _token Address of the token to recover or 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for ether (EIP-7528) * @param _recipient Address of the recovery recipient * @param _amount Amount of tokens or ether to recover */ function recoverERC20( address _token, address _recipient, uint256 _amount ) external onlyRoleMemberOrAdmin(DEFAULT_ADMIN_ROLE) { _requireNotZero(_token); _requireNotZero(_recipient); _requireNotZero(_amount); if (_token == RecoverTokens.ETH) { if (_amount > address(this).balance - feeLeftover) revert InsufficientBalance(); RecoverTokens._recoverEth(_recipient, _amount); } else { RecoverTokens._recoverERC20(_token, _recipient, _amount); } } /** * @notice Collects ERC20 tokens from vault contract balance to the recipient * @param _token Address of the token to collect * @param _recipient Address of the recipient * @param _amount Amount of tokens to collect * @dev will revert on EIP-7528 ETH address with EthCollectionNotAllowed() or on zero arguments with ZeroArgument() */ function collectERC20FromVault( address _token, address _recipient, uint256 _amount ) external onlyRoleMemberOrAdmin(COLLECT_VAULT_ERC20_ROLE) { VAULT_HUB.collectERC20FromVault(address(_stakingVault()), _token, _recipient, _amount); } /** * @notice Pauses beacon chain deposits on the StakingVault. */ function pauseBeaconChainDeposits() external { _pauseBeaconChainDeposits(); } /** * @notice Resumes beacon chain deposits on the StakingVault. */ function resumeBeaconChainDeposits() external { _resumeBeaconChainDeposits(); } /** * @notice Signals to node operators that specific validators should exit from the beacon chain. It DOES NOT * directly trigger the exit - node operators must monitor for request events and handle the exits. * @param _pubkeys Concatenated validator public keys (48 bytes each). * @dev Emits `ValidatorExitRequested` event for each validator public key through the `StakingVault`. * This is a voluntary exit request - node operators can choose whether to act on it or not. */ function requestValidatorExit(bytes calldata _pubkeys) external { _requestValidatorExit(_pubkeys); } /** * @notice Initiates a withdrawal from validator(s) on the beacon chain using EIP-7002 triggerable withdrawals * Both partial withdrawals (disabled for if vault is unhealthy) and full validator exits are supported. * @param _pubkeys Concatenated validator public keys (48 bytes each). * @param _amountsInGwei Withdrawal amounts in Gwei for each validator key. Must match _pubkeys length. * Set amount to 0 for a full validator exit. For partial withdrawals, amounts may be trimmed to keep * MIN_ACTIVATION_BALANCE on the validator to avoid deactivation. * @param _refundRecipient Address to receive any fee refunds * @dev A withdrawal fee must be paid via msg.value. * You can use `StakingVault.calculateValidatorWithdrawalFee()` to calculate the approximate fee amount but * it's accurate only for the current block. The fee may change when the tx is included, so it's recommended * to send some surplus. The exact amount required will be paid and the excess will be refunded to the * `_refundRecipient` address. The fee required can grow exponentially, so limit msg.value wisely to avoid * overspending. */ function triggerValidatorWithdrawals( bytes calldata _pubkeys, uint64[] calldata _amountsInGwei, address _refundRecipient ) external payable { _triggerValidatorWithdrawals(_pubkeys, _amountsInGwei, _refundRecipient); } /** * @notice Requests a change of tier on the OperatorGrid. * @param _tierId The tier to change to. * @param _requestedShareLimit The requested share limit. * @return bool True if the tier change was executed, false if pending for confirmation. * @dev Tier change confirmation logic: * - Both vault owner (via this function) AND node operator (via OperatorGrid) confirmations are always required * - First call returns false (pending), second call with both confirmations completes the tier change * - Confirmations expire after the configured period (default: 1 day) */ function changeTier(uint256 _tierId, uint256 _requestedShareLimit) external returns (bool) { return _changeTier(_tierId, _requestedShareLimit); } /** * @notice Requests a sync of tier on the OperatorGrid. * @return bool True if the tier sync was executed, false if pending for confirmation. * @dev Tier sync confirmation logic: * - Both vault owner (via this function) AND node operator (via OperatorGrid) confirmations are required * - First call returns false (pending), second call with both confirmations completes the operation * - Confirmations expire after the configured period (default: 1 day) */ function syncTier() external returns (bool) { return _syncTier(); } /** * @notice Requests a change of share limit on the OperatorGrid. * @param _requestedShareLimit The requested share limit. * @return bool True if the share limit change was executed, false if pending for confirmation. * @dev Share limit update confirmation logic: * - Both vault owner (via this function) AND node operator (via OperatorGrid) confirmations required * - First call returns false (pending), second call with node operator confirmation completes the operation * - Confirmations expire after the configured period (default: 1 day) */ function updateShareLimit(uint256 _requestedShareLimit) external returns (bool) { return _updateVaultShareLimit(_requestedShareLimit); } // ==================== Internal Functions ==================== /** * @dev Modifier to fund the staking vault if msg.value > 0 */ modifier fundable() { if (msg.value > 0) { _fund(msg.value); } _; } /** * @notice Mints shares within the mintable capacity, * and reverts if the resulting backing is greater than the mintable capacity. * @param _recipient The address of the recipient. * @param _amountOfShares The amount of shares to mint. */ function _mintSharesWithinMintingCapacity(address _recipient, uint256 _amountOfShares) internal { uint256 remainingShares = remainingMintingCapacityShares(0); if (_amountOfShares > remainingShares) revert ExceedsMintingCapacity(_amountOfShares, remainingShares); _mintShares(_recipient, _amountOfShares); } /** * @dev Burns stETH tokens from the sender backed by the vault * @param _amountOfStETH Amount of tokens to burn */ function _burnStETH(uint256 _amountOfStETH) internal { uint256 _amountOfShares = _getSharesByPooledEth(_amountOfStETH); STETH.transferSharesFrom(msg.sender, address(VAULT_HUB), _amountOfShares); _burnShares(_amountOfShares); } /** * @dev Burns wstETH tokens from the sender backed by the vault * @param _amountOfWstETH Amount of tokens to burn */ function _burnWstETH(uint256 _amountOfWstETH) internal { SafeERC20.safeTransferFrom(WSTETH, msg.sender, address(this), _amountOfWstETH); uint256 unwrappedStETH = WSTETH.unwrap(_amountOfWstETH); uint256 unwrappedShares = _getSharesByPooledEth(unwrappedStETH); STETH.transferShares(address(VAULT_HUB), unwrappedShares); _burnShares(unwrappedShares); } /// @notice Calculates the total number of shares that is possible to mint on the vault /// @dev the delta value is the amount of ether to add or subtract from the total value of the vault function _totalMintingCapacityShares(int256 _deltaValue) internal view returns (uint256) { return VAULT_HUB.totalMintingCapacityShares(address(_stakingVault()), _deltaValue); } /// @notice Converts the given amount of stETH to shares function _getSharesByPooledEth(uint256 _amountOfStETH) internal view returns (uint256) { return STETH.getSharesByPooledEth(_amountOfStETH); } // @dev The logic is inverted, 0 means fund-on-receive is enabled, // so that fund-on-receive is enabled by default function _shouldFundOnReceive() internal view returns (bool shouldFund) { assembly { shouldFund := iszero(tload(FUND_ON_RECEIVE_FLAG_SLOT)) } } function _enableFundOnReceive() internal { assembly { tstore(FUND_ON_RECEIVE_FLAG_SLOT, 0) } } function _disableFundOnReceive() internal { assembly { tstore(FUND_ON_RECEIVE_FLAG_SLOT, 1) } } /** * @dev Withdraws ether from vault to this contract for unguaranteed deposit to validators * Requires the caller to have the `NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE`. */ function _withdrawForUnguaranteedDepositToBeaconChain( uint256 _ether ) internal onlyRoleMemberOrAdmin(NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE) { VAULT_HUB.withdraw(address(_stakingVault()), address(this), _ether); } /** * @dev Proves validators unknown to PDG that have correct vault WC * Requires the caller to have the `NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE`. */ function _proveUnknownValidatorsToPDG( IPredepositGuarantee.ValidatorWitness[] calldata _witnesses ) internal onlyRoleMemberOrAdmin(NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE) { for (uint256 i = 0; i < _witnesses.length; i++) { VAULT_HUB.proveUnknownValidatorToPDG(address(_stakingVault()), _witnesses[i]); } } function _collectFeeLeftover() internal { (uint256 fee, int256 growth, uint256 abnormallyHighFeeThreshold) = _calculateFee(); if (fee > abnormallyHighFeeThreshold) revert AbnormallyHighFee(); if (fee > 0) { feeLeftover += uint128(fee); _disableFundOnReceive(); _disburseFee(fee, growth, address(this)); _enableFundOnReceive(); } } // ==================== Events ==================== /** * @notice Emitted when ether was withdrawn from the staking vault and deposited to validators directly bypassing PDG * @param stakingVault the address of owned staking vault * @param deposits the number of deposits * @param totalAmount the total amount of ether deposited to beacon chain */ event UnguaranteedDeposits(address indexed stakingVault, uint256 deposits, uint256 totalAmount); /** * @notice Emitted when the PDG policy is updated. */ event PDGPolicyEnacted(PDGPolicy pdgPolicy); // ==================== Errors ==================== /** * @notice Emitted when the withdrawable amount of ether is exceeded * @param amount The amount of ether that was attempted to be withdrawn * @param withdrawableValue The amount of withdrawable ether available */ error ExceedsWithdrawable(uint256 amount, uint256 withdrawableValue); /** * @notice Error thrown when minting capacity is exceeded */ error ExceedsMintingCapacity(uint256 requestedShares, uint256 remainingShares); /** * @notice Error when the StakingVault is still connected to the VaultHub. */ error ConnectedToVaultHub(); /** * @notice Error thrown when attempting to connect to VaultHub without confirmed tier change */ error TierChangeNotConfirmed(); /** * @notice Error when attempting to abandon the Dashboard contract itself. */ error DashboardNotAllowed(); /** * @notice Error when attempting to set the same PDG policy that is already active. */ error PDGPolicyAlreadyActive(); /** * @notice Error when attempting to perform an operation that is not allowed * by the current active PDG policy. */ error ForbiddenByPDGPolicy(); error InsufficientBalance(); /** * @dev Error emitted when connection is reverted because node operator's fee is stopped */ error SettleGrowthIsNotSet(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {VaultHub} from "../VaultHub.sol"; import {LazyOracle} from "../LazyOracle.sol"; import {Permissions} from "./Permissions.sol"; import {SafeCast} from "@openzeppelin/contracts-v5.2/utils/math/SafeCast.sol"; /** * @title NodeOperatorFee * @author Lido * @notice A contract that manages the node operator fee. */ contract NodeOperatorFee is Permissions { using SafeCast for uint256; using SafeCast for int256; /** * @notice Total basis points; 1bp = 0.01%, 100_00bp = 100.00%. */ uint256 internal constant TOTAL_BASIS_POINTS = 100_00; /** * @dev arbitrary number that is big enough to be infinite settled growth */ int256 internal constant MAX_SANE_SETTLED_GROWTH = type(int104).max; /** * @notice Parent role representing the node operator of the underlying StakingVault. * The members may not include the node operator address recorded in the underlying StakingVault * but it is assumed that the members of this role act in the interest of that node operator. * * @dev 0x59783a4ae82167eefad593739a5430c1d9e896a16c35f1e5285ddd0c0980885c */ bytes32 public constant NODE_OPERATOR_MANAGER_ROLE = keccak256("vaults.NodeOperatorFee.NodeOperatorManagerRole"); /** * @notice Node operator's sub-role for fee exemptions. * Managed by `NODE_OPERATOR_MANAGER_ROLE`. * * @dev 0xcceeef0309e9a678ed7f11f20499aeb00a9a4b0d50e53daa428f8591debc583a */ bytes32 public constant NODE_OPERATOR_FEE_EXEMPT_ROLE = keccak256("vaults.NodeOperatorFee.FeeExemptRole"); /** * @notice Node operator's sub-role for unguaranteed deposit * Managed by `NODE_OPERATOR_MANAGER_ROLE`. * * @dev 0x5c17b14b08ace6dda14c9642528ae92de2a73d59eacb65c71f39f309a5611063 */ bytes32 public constant NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE = keccak256("vaults.NodeOperatorFee.UnguaranteedDepositRole"); /** * @notice Node operator's sub-role for proving unknown validators. * Managed by `NODE_OPERATOR_MANAGER_ROLE`. * * @dev 0x7b564705f4e61596c4a9469b6884980f89e475befabdb849d69719f0791628be */ bytes32 public constant NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE = keccak256("vaults.NodeOperatorFee.ProveUnknownValidatorsRole"); /** * @notice If the accrued fee exceeds this BP of the total value, it is considered abnormally high. * An abnormally high fee can only be disbursed by `DEFAULT_ADMIN_ROLE`. * This threshold is to prevent accidental overpayment due to outdated settled growth. * * Why 1% threshold? * * - Assume a very generous annual staking APR of ~5% (3% CL + 2% EL). * - A very high node operator fee rate of 10% translates to a 0.5% annual fee. * - Thus, a 1% fee threshold would therefore be reached in 2 years. * - Meaning: as long as the operator disburses fees at least once every 2 years, * the threshold will never be hit. * * Since these assumptions are highly conservative, in practice the operator * would need to disburse even less frequently before approaching the threshold. */ uint256 constant internal ABNORMALLY_HIGH_FEE_THRESHOLD_BP = 1_00; // ==================== Packed Storage Slot 1 ==================== /** * @notice Address that receives node operator fee disbursements. * This address is set by the node operator manager and receives disbursed fees. */ address public feeRecipient; /** * @notice Node operator fee rate in basis points (1 bp = 0.01%). * Cannot exceed 100.00% (10000 basis points). */ uint16 public feeRate; // ==================== Packed Storage Slot 2 ==================== /** * @notice Growth of the vault not subject to fees. * * Growth is the difference between inOutDelta and totalValue, * i.e. the component of totalValue that has not been directly funded to the underlying StakingVault via `fund()`: * inOutDelta + growth = totalValue * * Settled growth is the portion of the total growth that: * - has already been charged by the node operator, * - or is not subject to fee (exempted) such as unguaranteed/side deposits, consolidations. */ int128 public settledGrowth; /** * @notice Timestamp of the most recent settled growth correction. * This timestamp is used to prevent retroactive fees after a fee rate change. * The timestamp ensures that all fee exemptions and corrections are fully reported before changing the fee rate. * Regular fee disbursements do not update this timestamp. */ uint64 public latestCorrectionTimestamp; /** * @notice Passes the address of the vault hub up the inheritance chain. * @param _vaultHub The address of the vault hub. * @param _lidoLocator The address of the Lido locator. */ constructor(address _vaultHub, address _lidoLocator) Permissions(_vaultHub, _lidoLocator) {} /** * @dev Calls the parent's initializer, sets the node operator fee, assigns the node operator manager role, * and makes the node operator manager the admin for the node operator roles. * @param _defaultAdmin The address of the default admin * @param _nodeOperatorManager The address of the node operator manager * @param _feeRecipient The node operator fee recipient address * @param _feeRate The node operator fee rate * @param _confirmExpiry The confirmation expiry time in seconds */ function _initialize( address _defaultAdmin, address _nodeOperatorManager, address _feeRecipient, uint256 _feeRate, uint256 _confirmExpiry ) internal { _requireNotZero(_nodeOperatorManager); super._initialize(_defaultAdmin, _confirmExpiry); _setFeeRate(_feeRate); _setFeeRecipient(_feeRecipient); _grantRole(NODE_OPERATOR_MANAGER_ROLE, _nodeOperatorManager); _setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE); _setRoleAdmin(NODE_OPERATOR_FEE_EXEMPT_ROLE, NODE_OPERATOR_MANAGER_ROLE); _setRoleAdmin(NODE_OPERATOR_UNGUARANTEED_DEPOSIT_ROLE, NODE_OPERATOR_MANAGER_ROLE); _setRoleAdmin(NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE, NODE_OPERATOR_MANAGER_ROLE); } /** * @notice The roles that must confirm critical parameter changes in the contract. * @return roles is an array of roles that form the confirming roles. */ function confirmingRoles() public pure returns (bytes32[] memory roles) { roles = new bytes32[](2); roles[0] = DEFAULT_ADMIN_ROLE; roles[1] = NODE_OPERATOR_MANAGER_ROLE; } /** * @notice The latest vault report for the underlying StakingVault. * @return report The latest report containing totalValue, inOutDelta, and timestamp */ function latestReport() public view returns (VaultHub.Report memory) { return VAULT_HUB.latestReport(address(_stakingVault())); } /** * @notice Calculates the current node operator fee amount in ETH. * * Fee calculation steps: * 1. Retrieve latest vault report (totalValue, inOutDelta) * 2. Calculate current growth: totalValue - inOutDelta * 3. Determine unsettled growth: currentGrowth - settledGrowth * 4. Apply fee rate: unsettledGrowth × feeRate / 10000 * * @return fee The amount of ETH accrued as fee */ function accruedFee() public view returns (uint256 fee) { (fee,, ) = _calculateFee(); } /** * @notice Disburses node operator fees permissionlessly. * Can be called by anyone as long as fee is not abnormally high. * * Fee disbursement steps: * 1. Calculate current vault growth from latest report * 2. Determine fee amount on unsettled growth * 3. Update settled growth to current growth (marking fees as paid) * 4. Withdraws fee amount from vault to node operator recipient */ function disburseFee() public { (uint256 fee, int256 growth, uint256 abnormallyHighFeeThreshold) = _calculateFee(); if (fee > abnormallyHighFeeThreshold) revert AbnormallyHighFee(); _disburseFee(fee, growth, feeRecipient); } /** * @notice Disburses an abnormally high fee as `DEFAULT_ADMIN_ROLE`. * Before calling this function, the caller must ensure that the high fee is expected, * and the settled growth (used as baseline for fee) is set correctly. */ function disburseAbnormallyHighFee() external onlyRoleMemberOrAdmin(DEFAULT_ADMIN_ROLE) { (uint256 fee, int256 growth,) = _calculateFee(); _disburseFee(fee, growth, feeRecipient); } /** * @notice Updates the node operator's fee rate with dual confirmation. * @param _newFeeRate The new fee rate in basis points (max 10000 = 100%) * @return bool True if fee rate was updated, false if still awaiting confirmations */ function setFeeRate(uint256 _newFeeRate) external returns (bool) { // The report must be fresh so that the total value of the vault is up to date // and all the node operator fees are paid out fairly up to the moment of the latest fresh report if (!VAULT_HUB.isReportFresh(address(_stakingVault()))) revert ReportStale(); // Latest fee exemption must be earlier than the latest fresh report timestamp if (latestCorrectionTimestamp >= _lazyOracle().latestReportTimestamp()) revert CorrectionAfterReport(); // store the caller's confirmation; only proceed if the required number of confirmations is met. if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; // Disburse any outstanding fees at the current rate before changing it disburseFee(); _setFeeRate(_newFeeRate); return true; } /** * @notice Manually corrects the settled growth value with dual confirmation. * Used to correct fee calculation and enable fee accrual after reconnection * * So, in the simplest case the value of settledGrowth before the vault is connected to VaultHub should be set to: * * sum(validator.balance) + stagedBalance * * @param _newSettledGrowth The corrected settled growth value * @param _expectedSettledGrowth The expected current settled growth * @return bool True if correction was applied, false if awaiting confirmations */ function correctSettledGrowth(int256 _newSettledGrowth, int256 _expectedSettledGrowth) external returns (bool) { if (settledGrowth != _expectedSettledGrowth) revert UnexpectedSettledGrowth(); if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; _correctSettledGrowth(_newSettledGrowth); return true; } /** * @notice Adds a fee exemption to exclude this value from node operator fee base. * The exemption works by increasing the settled growth, * effectively treating the exempted amount as if fees were already paid on it. * * @param _exemptedAmount Amount in ETH to exempt from fee calculations */ function addFeeExemption(uint256 _exemptedAmount) external onlyRoleMemberOrAdmin(NODE_OPERATOR_FEE_EXEMPT_ROLE) { _addFeeExemption(_exemptedAmount); } /** * @notice Sets the confirmation expiry period with dual confirmation. * @param _newConfirmExpiry The new confirmation expiry period in seconds * @return bool True if expiry was updated, false if awaiting confirmations */ function setConfirmExpiry(uint256 _newConfirmExpiry) external returns (bool) { _validateConfirmExpiry(_newConfirmExpiry); if (!_collectAndCheckConfirmations(msg.data, confirmingRoles())) return false; _setConfirmExpiry(_newConfirmExpiry); return true; } /** * @notice Sets the address that receives node operator fee disbursements. * @param _newFeeRecipient The new recipient address for fee payments */ function setFeeRecipient(address _newFeeRecipient) external onlyRoleMemberOrAdmin(NODE_OPERATOR_MANAGER_ROLE) { _setFeeRecipient(_newFeeRecipient); } // ==================== Internal Functions ==================== function _lazyOracle() internal view returns (LazyOracle) { return LazyOracle(LIDO_LOCATOR.lazyOracle()); } function _disburseFee(uint256 fee, int256 growth, address _recipient) internal { if (fee == 0) { // we still need to update the settledGrowth event if the fee is zero // to avoid the retroactive fees if (growth > settledGrowth) _setSettledGrowth(growth); return; } _setSettledGrowth(growth); _doWithdraw(_recipient, fee); emit FeeDisbursed(msg.sender, fee, _recipient); } function _setSettledGrowth(int256 _newSettledGrowth) internal { int256 oldSettledGrowth = settledGrowth; if (oldSettledGrowth == _newSettledGrowth) revert SameSettledGrowth(); settledGrowth = _newSettledGrowth.toInt128(); emit SettledGrowthSet(oldSettledGrowth, _newSettledGrowth); } /** * @dev Set a new settled growth and updates the timestamp. * Should be used to correct settled growth for total value change that might not have been reported yet */ function _correctSettledGrowth(int256 _newSettledGrowth) internal { _setSettledGrowth(_newSettledGrowth); latestCorrectionTimestamp = uint64(block.timestamp); emit CorrectionTimestampUpdated(block.timestamp); } /** * @dev Increases settled growth for total value increases not subject to fee, * which is why it updates the timestamp to ensure that the exemption comes before * the total value report during the fee rate change, which guarantees that the exemption is reported * @dev fee exemption can only be positive */ function _addFeeExemption(uint256 _amount) internal { if (_amount > uint256(MAX_SANE_SETTLED_GROWTH)) revert UnexpectedFeeExemptionAmount(); _correctSettledGrowth(settledGrowth + int256(_amount)); } function _calculateFee() internal view returns (uint256 fee, int256 growth, uint256 abnormallyHighFeeThreshold) { VaultHub.Report memory report = latestReport(); // we include quarantined value for fees as well uint256 quarantineValue = _lazyOracle().quarantineValue(address(_stakingVault())); uint256 totalValueAndQuarantine = uint256(report.totalValue) + quarantineValue; growth = int256(totalValueAndQuarantine) - report.inOutDelta; int256 unsettledGrowth = growth - settledGrowth; if (unsettledGrowth > 0) { fee = (uint256(unsettledGrowth) * feeRate) / TOTAL_BASIS_POINTS; } abnormallyHighFeeThreshold = (totalValueAndQuarantine * ABNORMALLY_HIGH_FEE_THRESHOLD_BP) / TOTAL_BASIS_POINTS; } function _stopFeeAccrual() internal { // effectively stopping fee accrual by setting over the top settledGrowth if (settledGrowth < MAX_SANE_SETTLED_GROWTH) _setSettledGrowth(MAX_SANE_SETTLED_GROWTH); } function _setFeeRate(uint256 _newFeeRate) internal { if (_newFeeRate > TOTAL_BASIS_POINTS) revert FeeValueExceed100Percent(); uint256 oldFeeRate = feeRate; uint256 newFeeRate = _newFeeRate; feeRate = uint16(newFeeRate); emit FeeRateSet(msg.sender, oldFeeRate, newFeeRate); } function _setFeeRecipient(address _newFeeRecipient) internal { _requireNotZero(_newFeeRecipient); if (_newFeeRecipient == feeRecipient) revert SameRecipient(); address oldFeeRecipient = feeRecipient; feeRecipient = _newFeeRecipient; emit FeeRecipientSet(msg.sender, oldFeeRecipient, _newFeeRecipient); } // ==================== Events ==================== /** * @dev Emitted when the node operator fee is set. * @param sender the address of the sender * @param oldFeeRate The old node operator fee rate. * @param newFeeRate The new node operator fee rate. */ event FeeRateSet(address indexed sender, uint256 oldFeeRate, uint256 newFeeRate); /** * @dev Emitted when the node operator fee is disbursed. * @param sender the address of the sender * @param fee the amount of disbursed fee. * @param recipient the address of recipient */ event FeeDisbursed(address indexed sender, uint256 fee, address recipient); /** * @dev Emitted when the node operator fee recipient is set. * @param sender the address of the sender who set the recipient * @param oldFeeRecipient the old node operator fee recipient * @param newFeeRecipient the new node operator fee recipient */ event FeeRecipientSet(address indexed sender, address oldFeeRecipient, address newFeeRecipient); /** * @dev Emitted when the settled growth is set. * @param oldSettledGrowth the old settled growth * @param newSettledGrowth the new settled growth */ event SettledGrowthSet(int256 oldSettledGrowth, int256 newSettledGrowth); /** * @dev Emitted when the settled growth is corrected. * @param timestamp new correction timestamp */ event CorrectionTimestampUpdated(uint256 timestamp); // ==================== Errors ==================== /** * @dev Error emitted when the combined feeBPs exceed 100%. */ error FeeValueExceed100Percent(); /** * @dev Error emitted when trying to disburse an abnormally high fee. */ error AbnormallyHighFee(); /** * @dev Error emitted when trying to set same value for recipient */ error SameRecipient(); /** * @dev Error emitted when trying to set same value for settled growth */ error SameSettledGrowth(); /** * @dev Error emitted when the report is stale. */ error ReportStale(); /** * @dev Error emitted when the correction is made after the report. */ error CorrectionAfterReport(); /** * @dev Error emitted when the settled growth does not match the expected value. */ error UnexpectedSettledGrowth(); /** * @dev Error emitted when the fee exemption amount does not match the expected value */ error UnexpectedFeeExemptionAmount(); }
// SPDX-License-Identifier: GPL-3.0 // SPDX-FileCopyrightText: 2025 Lido <[email protected]> // See contracts/COMPILERS.md pragma solidity 0.8.25; import {Clones} from "@openzeppelin/contracts-v5.2/proxy/Clones.sol"; import {AccessControl} from "@openzeppelin/contracts-v5.2/access/AccessControl.sol"; import {IAccessControl} from "@openzeppelin/contracts-v5.2/access/IAccessControl.sol"; import {AccessControlConfirmable} from "contracts/0.8.25/utils/AccessControlConfirmable.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {IStakingVault} from "../interfaces/IStakingVault.sol"; import {OperatorGrid} from "../OperatorGrid.sol"; import {VaultHub} from "../VaultHub.sol"; /** * @title Permissions * @author Lido * @notice Provides granular permissions for StakingVault operations. */ abstract contract Permissions is AccessControlConfirmable { /** * @notice Struct containing an account and a role for granting/revoking roles. */ struct RoleAssignment { address account; bytes32 role; } /** * @notice Permission for funding the StakingVault. */ /// @dev 0x933b7d5c112a4d05b489cea0b2ced98acb27d3d0fc9827c92cdacb2d6c5559c2 bytes32 public constant FUND_ROLE = keccak256("vaults.Permissions.Fund"); /** * @notice Permission for withdrawing funds from the StakingVault. */ /// @dev 0x355caf1c2580ed8185acb5ea3573b71f85186b41bdf69e3eb8f1fcd122a562df bytes32 public constant WITHDRAW_ROLE = keccak256("vaults.Permissions.Withdraw"); /** * @notice Permission for minting stETH shares backed by the StakingVault. */ /// @dev 0xe996ac9b332538bb1fa3cd6743aa47011623cdb94bd964a494ee9d371e4a27d3 bytes32 public constant MINT_ROLE = keccak256("vaults.Permissions.Mint"); /** * @notice Permission for burning stETH shares backed by the StakingVault. */ /// @dev 0x689f0a569be0c9b6cd2c11c81cb0add722272abdae6b649fdb1e05f1d9bb8a2f bytes32 public constant BURN_ROLE = keccak256("vaults.Permissions.Burn"); /** * @notice Permission for rebalancing the StakingVault. */ /// @dev 0x3f82ecf462ddac43fc17ba11472c35f18b7760b4f5a5fc50b9625f9b5a22cf62 bytes32 public constant REBALANCE_ROLE = keccak256("vaults.Permissions.Rebalance"); /** * @notice Permission for pausing beacon chain deposits on the StakingVault. */ /// @dev 0xa90c7030a27f389f9fc8ed21a0556f40c88130cc14a80db936bed68261819b2c bytes32 public constant PAUSE_BEACON_CHAIN_DEPOSITS_ROLE = keccak256("vaults.Permissions.PauseDeposits"); /** * @notice Permission for resuming beacon chain deposits on the StakingVault. */ /// @dev 0x59d005e32db662b94335d6bedfeb453fd2202b9f0cc7a6ed498d9098171744b0 bytes32 public constant RESUME_BEACON_CHAIN_DEPOSITS_ROLE = keccak256("vaults.Permissions.ResumeDeposits"); /** * @notice Permission for requesting validator exit from the StakingVault. */ /// @dev 0x32d0d6546e21c13ff633616141dc9daad87d248d1d37c56bf493d06d627ecb7b bytes32 public constant REQUEST_VALIDATOR_EXIT_ROLE = keccak256("vaults.Permissions.RequestValidatorExit"); /** * @notice Permission for triggering validator withdrawal from the StakingVault using EIP-7002 triggerable exit. */ /// @dev 0xea19d3b23bd90fdd52445ad672f2b6fb1fef7230d49c6a827c1cd288d02994d5 bytes32 public constant TRIGGER_VALIDATOR_WITHDRAWAL_ROLE = keccak256("vaults.Permissions.TriggerValidatorWithdrawal"); /** * @notice Permission for voluntary disconnecting the StakingVault. */ /// @dev 0x9586321ac05f110e4b4a0a42aba899709345af0ca78910e8832ddfd71fed2bf4 bytes32 public constant VOLUNTARY_DISCONNECT_ROLE = keccak256("vaults.Permissions.VoluntaryDisconnect"); /** * @dev Permission for vault configuration operations on the OperatorGrid (tier changes, tier sync, share limit updates). */ /// @dev 0x25482e7dc9e29f6da5bd70b6d19d17bbf44021da51ba0664a9f430c94a09c674 bytes32 public constant VAULT_CONFIGURATION_ROLE = keccak256("vaults.Permissions.VaultConfiguration"); VaultHub public immutable VAULT_HUB; ILidoLocator public immutable LIDO_LOCATOR; /** * @notice Indicates whether the contract has been initialized */ bool public initialized; constructor(address _vaultHub, address _lidoLocator) { _requireNotZero(_vaultHub); _requireNotZero(_lidoLocator); initialized = true; // @dev vaultHub is cached as immutable to save gas for main operations VAULT_HUB = VaultHub(payable(_vaultHub)); LIDO_LOCATOR = ILidoLocator(_lidoLocator); } /** * @notice Modifier to prevent reinitialization of the contract. * @dev Extracted to modifier to avoid Slither warning. */ modifier initializer() { if (initialized) revert AlreadyInitialized(); initialized = true; _; emit Initialized(); } /** * @dev Sets the ACL default admin and confirmation expiry time. * @param _defaultAdmin The address of the default admin * @param _confirmExpiry The confirmation expiry time in seconds */ function _initialize(address _defaultAdmin, uint256 _confirmExpiry) internal initializer { _requireNotZero(_defaultAdmin); _grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); _validateConfirmExpiry(_confirmExpiry); _setConfirmExpiry(_confirmExpiry); } /** * @notice Returns the address of the underlying StakingVault. * @return The address of the StakingVault. */ function stakingVault() external view returns (IStakingVault) { return _stakingVault(); } // ==================== Role Management Functions ==================== /** * @notice Mass-grants multiple roles to multiple accounts. * @param _assignments An array of role assignments. * @dev Performs the role admin checks internally. * @dev If an account is already a member of a role, doesn't revert, emits no events. */ function grantRoles(RoleAssignment[] calldata _assignments) external { _requireNotZero(_assignments.length); for (uint256 i = 0; i < _assignments.length; i++) { grantRole(_assignments[i].role, _assignments[i].account); } } /** * @notice Mass-revokes multiple roles from multiple accounts. * @param _assignments An array of role assignments. * @dev Performs the role admin checks internally. * @dev If an account is not a member of a role, doesn't revert, emits no events. */ function revokeRoles(RoleAssignment[] calldata _assignments) external { _requireNotZero(_assignments.length); for (uint256 i = 0; i < _assignments.length; i++) { revokeRole(_assignments[i].role, _assignments[i].account); } } /** * @notice Role renouncement is disabled to avoid accidental access loss. */ function renounceRole(bytes32, address) public pure override(AccessControl, IAccessControl) { revert RoleRenouncementDisabled(); } /** * @dev A custom modifier that checks if the caller has a role or the admin role for a given role. * @param _role The role to check. */ modifier onlyRoleMemberOrAdmin(bytes32 _role) { if (!(hasRole(_role, msg.sender) || hasRole(getRoleAdmin(_role), msg.sender))) { revert AccessControlUnauthorizedAccount(msg.sender, _role); } _; } /** * @dev Checks the FUND_ROLE and funds the StakingVault. * @param _ether The amount of ether to fund the StakingVault with. */ function _fund(uint256 _ether) internal onlyRoleMemberOrAdmin(FUND_ROLE) { VAULT_HUB.fund{value: _ether}(address(_stakingVault())); } /** * @dev Checks the WITHDRAW_ROLE and withdraws funds from the StakingVault. * @param _recipient The address to withdraw the funds to. * @param _ether The amount of ether to withdraw from the StakingVault. */ function _withdraw(address _recipient, uint256 _ether) internal virtual onlyRoleMemberOrAdmin(WITHDRAW_ROLE) { _doWithdraw(_recipient, _ether); } /** * @dev Checks the MINT_ROLE and mints shares backed by the StakingVault. * @param _recipient The address to mint the shares to. * @param _shares The amount of shares to mint. */ function _mintShares(address _recipient, uint256 _shares) internal onlyRoleMemberOrAdmin(MINT_ROLE) { VAULT_HUB.mintShares(address(_stakingVault()), _recipient, _shares); } /** * @dev Checks the BURN_ROLE and burns shares backed by the StakingVault. * @param _shares The amount of shares to burn. */ function _burnShares(uint256 _shares) internal onlyRoleMemberOrAdmin(BURN_ROLE) { VAULT_HUB.burnShares(address(_stakingVault()), _shares); } /** * @dev Checks the REBALANCE_ROLE and rebalances the StakingVault. * @param _shares The amount of shares to rebalance the StakingVault with. */ function _rebalanceVault(uint256 _shares) internal onlyRoleMemberOrAdmin(REBALANCE_ROLE) { VAULT_HUB.rebalance(address(_stakingVault()), _shares); } /** * @dev Checks the PAUSE_BEACON_CHAIN_DEPOSITS_ROLE and pauses beacon chain deposits on the StakingVault. */ function _pauseBeaconChainDeposits() internal onlyRoleMemberOrAdmin(PAUSE_BEACON_CHAIN_DEPOSITS_ROLE) { VAULT_HUB.pauseBeaconChainDeposits(address(_stakingVault())); } /** * @dev Checks the RESUME_BEACON_CHAIN_DEPOSITS_ROLE and resumes beacon chain deposits on the StakingVault. */ function _resumeBeaconChainDeposits() internal onlyRoleMemberOrAdmin(RESUME_BEACON_CHAIN_DEPOSITS_ROLE) { VAULT_HUB.resumeBeaconChainDeposits(address(_stakingVault())); } /** * @dev Checks the REQUEST_VALIDATOR_EXIT_ROLE and requests validator exit on the StakingVault. */ function _requestValidatorExit( bytes calldata _pubkeys ) internal onlyRoleMemberOrAdmin(REQUEST_VALIDATOR_EXIT_ROLE) { VAULT_HUB.requestValidatorExit(address(_stakingVault()), _pubkeys); } /** * @dev Checks the TRIGGER_VALIDATOR_WITHDRAWAL_ROLE and triggers validator withdrawal on the StakingVault * using EIP-7002 triggerable exit. */ function _triggerValidatorWithdrawals( bytes calldata _pubkeys, uint64[] calldata _amountsInGwei, address _refundRecipient ) internal onlyRoleMemberOrAdmin(TRIGGER_VALIDATOR_WITHDRAWAL_ROLE) { VAULT_HUB.triggerValidatorWithdrawals{value: msg.value}( address(_stakingVault()), _pubkeys, _amountsInGwei, _refundRecipient ); } /** * @dev Checks the VOLUNTARY_DISCONNECT_ROLE and voluntarily disconnects the StakingVault. */ function _voluntaryDisconnect() internal onlyRoleMemberOrAdmin(VOLUNTARY_DISCONNECT_ROLE) { VAULT_HUB.voluntaryDisconnect(address(_stakingVault())); } /** * @dev Checks the DEFAULT_ADMIN_ROLE and transfers the StakingVault ownership. * @param _newOwner The address to transfer the ownership to. */ function _transferOwnership(address _newOwner) internal onlyRole(DEFAULT_ADMIN_ROLE) { _stakingVault().transferOwnership(_newOwner); } /** * @dev Checks the DEFAULT_ADMIN_ROLE and accepts the StakingVault ownership. */ function _acceptOwnership() internal onlyRole(DEFAULT_ADMIN_ROLE) { _stakingVault().acceptOwnership(); } /** * @dev Checks the VAULT_CONFIGURATION_ROLE and requests a change of the tier on the OperatorGrid. * @param _tierId The tier to change to. * @param _requestedShareLimit The requested share limit. * @return bool Whether the tier change was executed. */ function _changeTier( uint256 _tierId, uint256 _requestedShareLimit ) internal onlyRoleMemberOrAdmin(VAULT_CONFIGURATION_ROLE) returns (bool) { return _operatorGrid().changeTier(address(_stakingVault()), _tierId, _requestedShareLimit); } /** * @dev Checks the VAULT_CONFIGURATION_ROLE and requests a sync of the tier on the OperatorGrid. * @return bool Whether the tier sync was executed. */ function _syncTier() internal onlyRoleMemberOrAdmin(VAULT_CONFIGURATION_ROLE) returns (bool) { return _operatorGrid().syncTier(address(_stakingVault())); } /** * @dev Checks the VAULT_CONFIGURATION_ROLE and updates the share limit on the OperatorGrid. * @param _requestedShareLimit The requested share limit. * @return bool Whether the share limit update was executed. */ function _updateVaultShareLimit(uint256 _requestedShareLimit) internal onlyRoleMemberOrAdmin(VAULT_CONFIGURATION_ROLE) returns (bool) { return _operatorGrid().updateVaultShareLimit(address(_stakingVault()), _requestedShareLimit); } /** * @dev Loads the address of the underlying StakingVault. * @return addr The address of the StakingVault. */ function _stakingVault() internal view returns (IStakingVault) { bytes memory args = Clones.fetchCloneArgs(address(this)); address stakingVaultAddress; assembly { stakingVaultAddress := mload(add(args, 32)) } return IStakingVault(stakingVaultAddress); } /// @dev internal withdraw function just to save the bytecode for external call method function _doWithdraw(address _recipient, uint256 _ether) internal { VAULT_HUB.withdraw(address(_stakingVault()), _recipient, _ether); } function _operatorGrid() internal view returns (OperatorGrid) { return OperatorGrid(LIDO_LOCATOR.operatorGrid()); } function _requireNotZero(uint256 _value) internal pure { if (_value == 0) revert ZeroArgument(); } function _requireNotZero(address _address) internal pure { if (_address == address(0)) revert ZeroAddress(); } /** * @notice Emitted when the contract is initialized */ event Initialized(); /** * @notice Error when the contract is already initialized. */ error AlreadyInitialized(); /** * @notice Error thrown for when a given value cannot be zero */ error ZeroArgument(); /** * @notice Error thrown for when a given address cannot be zero */ error ZeroAddress(); /** * @notice Error thrown when attempting to renounce a role. */ error RoleRenouncementDisabled(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.8.0; /** * @title IPinnedBeaconProxy * @author Lido * @notice Interface for the `PinnedBeaconProxy` contract */ interface IPinnedBeaconProxy { function isOssified() external view returns (bool); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.8.0; import {IStakingVault} from "./IStakingVault.sol"; /** * @title IPredepositGuarantee * @author Lido * @notice Interface for the `PredepositGuarantee` contract */ interface IPredepositGuarantee { /** * @notice represents validator stages in PDG flow * @param NONE - initial stage * @param PREDEPOSITED - PREDEPOSIT_AMOUNT is deposited to this validator by the vault * @param PROVEN - validator is proven to be valid and can be used to deposit to beacon chain * @param ACTIVATED - validator is proven and the ACTIVATION_DEPOSIT_AMOUNT is deposited to this validator * @param COMPENSATED - disproven validator has its PREDEPOSIT_AMOUNT ether compensated to staking vault owner and validator cannot be used in PDG anymore */ enum ValidatorStage { NONE, PREDEPOSITED, PROVEN, ACTIVATED, COMPENSATED } /** * @notice represents status of the validator in PDG * @param stage represents validator stage in PDG flow * @param stakingVault pins validator to specific StakingVault * @param nodeOperator pins validator to specific NO */ struct ValidatorStatus { ValidatorStage stage; IStakingVault stakingVault; address nodeOperator; } /** * @notice user input for validator proof verification * @custom:proof array of merkle proofs from parent(pubkey,wc) node to Beacon block root * @custom:pubkey of validator to prove * @custom:validatorIndex of validator in CL state tree * @custom:childBlockTimestamp of EL block that has parent block beacon root in BEACON_ROOTS contract * @custom:slot of the beacon block for which the proof is generated * @custom:proposerIndex of the beacon block for which the proof is generated */ struct ValidatorWitness { bytes32[] proof; bytes pubkey; uint256 validatorIndex; uint64 childBlockTimestamp; uint64 slot; uint64 proposerIndex; } function pendingActivations(IStakingVault _vault) external view returns (uint256); function validatorStatus(bytes calldata _pubkey) external view returns (ValidatorStatus memory); function proveUnknownValidator(ValidatorWitness calldata _witness, IStakingVault _stakingVault) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.8.0; import {IDepositContract} from "contracts/common/interfaces/IDepositContract.sol"; /** * @title IStakingVault * @author Lido * @notice Interface for the `StakingVault` contract */ interface IStakingVault { /** * @notice validator deposit from the `StakingVault` to the beacon chain * @dev withdrawal credentials are provided by the vault * @custom:pubkey The validator's BLS public key (48 bytes) * @custom:signature BLS signature of the deposit data (96 bytes) * @custom:amount Amount of ETH to deposit in wei (must be a multiple of 1 Gwei and minimum of 1 ETH) * @custom:depositDataRoot The root hash of the deposit data per ETH beacon spec */ struct Deposit { bytes pubkey; bytes signature; uint256 amount; bytes32 depositDataRoot; } function DEPOSIT_CONTRACT() external view returns (IDepositContract); function initialize(address _owner, address _nodeOperator, address _depositor) external; function version() external pure returns (uint64); function getInitializedVersion() external view returns (uint64); function withdrawalCredentials() external view returns (bytes32); function owner() external view returns (address); function pendingOwner() external view returns (address); function acceptOwnership() external; function transferOwnership(address _newOwner) external; function nodeOperator() external view returns (address); function depositor() external view returns (address); function calculateValidatorWithdrawalFee(uint256 _keysCount) external view returns (uint256); function fund() external payable; function withdraw(address _recipient, uint256 _ether) external; function beaconChainDepositsPaused() external view returns (bool); function pauseBeaconChainDeposits() external; function resumeBeaconChainDeposits() external; function depositToBeaconChain(Deposit calldata _deposits) external; function requestValidatorExit(bytes calldata _pubkeys) external; function triggerValidatorWithdrawals(bytes calldata _pubkeys, uint64[] calldata _amountsInGwei, address _refundRecipient) external payable; function ejectValidators(bytes calldata _pubkeys, address _refundRecipient) external payable; function setDepositor(address _depositor) external; function ossify() external; function collectERC20(address _token, address _recipient, uint256 _amount) external; function availableBalance() external view returns (uint256); function stagedBalance() external view returns (uint256); function stage(uint256 _ether) external; function unstage(uint256 _ether) external; function depositFromStaged(Deposit calldata _deposit, uint256 _additionalAmount) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; interface IVaultFactory { function deployedVaults(address _vault) external view returns (bool); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {MerkleProof} from "@openzeppelin/contracts-v5.2/utils/cryptography/MerkleProof.sol"; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {ILazyOracle} from "contracts/common/interfaces/ILazyOracle.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {ILido} from "contracts/common/interfaces/ILido.sol"; import {VaultHub} from "./VaultHub.sol"; import {OperatorGrid} from "./OperatorGrid.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {IPredepositGuarantee} from "./interfaces/IPredepositGuarantee.sol"; import {DoubleRefSlotCache, DOUBLE_CACHE_LENGTH} from "./lib/RefSlotCache.sol"; contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable { using DoubleRefSlotCache for DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH]; enum QuarantineState { NO_QUARANTINE, // No active quarantine QUARANTINE_ACTIVE, // Quarantine active, not expired QUARANTINE_EXPIRED // Quarantine period has passed } /// @custom:storage-location erc7201:Lido.Vaults.LazyOracle struct Storage { /// @notice root of the vaults data tree bytes32 vaultsDataTreeRoot; /// @notice CID of the vaults data tree string vaultsDataReportCid; /// @notice timestamp of the vaults data uint64 vaultsDataTimestamp; /// @notice refSlot of the vaults data uint48 vaultsDataRefSlot; /// @notice total value increase quarantine period uint64 quarantinePeriod; /// @notice max reward ratio for refSlot-observed total value, basis points uint16 maxRewardRatioBP; /// @notice max Lido fee rate per second, in wei uint64 maxLidoFeeRatePerSecond; // 64 bit is enough for up to 18 ETH/s /// @notice deposit quarantines for each vault mapping(address vault => Quarantine) vaultQuarantines; } /* A quarantine is a timelock applied to any sudden jump in a vault's reported total value that cannot be immediately confirmed on-chain (via the inOutDelta difference). If the reported total value exceeds the expected routine EL/CL rewards, the excess is pushed into a quarantine buffer for a predefined cooldown period. Only after this delay is the quarantined value released into VaultHub's total value. Normal top-ups — where the vault owner funds the contract directly using the `fund()` function — do not go through quarantine, as they can be verified on-chain via the inOutDelta value. These direct fundings are reflected immediately. In contrast, consolidations or deposits that bypass the vault's balance must sit in quarantine. Example flow: Time 0: Total Value = 100 ETH ┌────────────────────────────────────┐ │ 100 ETH Active │ └────────────────────────────────────┘ Time 1: Sudden jump of +50 ETH → start quarantine for 50 ETH ┌────────────────────────────────────┐ │ 100 ETH Active │ │ 50 ETH Quarantined │ └────────────────────────────────────┘ Time 2: Another jump of +70 ETH → wait for current quarantine to expire ┌────────────────────────────────────┐ │ 100 ETH Active │ │ 50 ETH Quarantined │ │ 70 ETH Quarantine Queue │ └────────────────────────────────────┘ Time 3: First quarantine expires → add 50 ETH to active value, start new quarantine for 70 ETH ┌────────────────────────────────────┐ │ 150 ETH Active │ │ 70 ETH Quarantined │ └────────────────────────────────────┘ Time 4: Second quarantine expires → add 70 ETH to active value ┌────────────────────────────────────┐ │ 220 ETH Active │ └────────────────────────────────────┘ */ struct Quarantine { uint128 pendingTotalValueIncrease; uint64 startTimestamp; uint128 totalValueRemainder; } struct QuarantineInfo { bool isActive; uint256 pendingTotalValueIncrease; uint256 startTimestamp; uint256 endTimestamp; uint256 totalValueRemainder; } struct VaultInfo { address vault; uint256 aggregatedBalance; // includes availableBalance and stagedBalance int256 inOutDelta; bytes32 withdrawalCredentials; uint256 liabilityShares; uint256 maxLiabilityShares; uint256 mintableStETH; uint96 shareLimit; uint16 reserveRatioBP; uint16 forcedRebalanceThresholdBP; uint16 infraFeeBP; uint16 liquidityFeeBP; uint16 reservationFeeBP; bool pendingDisconnect; } // keccak256(abi.encode(uint256(keccak256("Lido.Vaults.LazyOracle")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant LAZY_ORACLE_STORAGE_LOCATION = 0x73a2a247d4b1b6fe056fe90935e9bd3694e896bafdd08f046c2afe6ec2db2100; /// @dev 0x7baf7f4a9784fa74c97162de631a3eb567edeb85878cb6965945310f2c512c63 bytes32 public constant UPDATE_SANITY_PARAMS_ROLE = keccak256("vaults.LazyOracle.UpdateSanityParams"); ILidoLocator public immutable LIDO_LOCATOR; /// @dev basis points base uint256 private constant TOTAL_BASIS_POINTS = 100_00; uint256 private constant MAX_SANE_TOTAL_VALUE = type(uint96).max; uint256 public constant MAX_QUARANTINE_PERIOD = 30 days; /// @dev max value for reward ratio - it's about 650% uint256 public constant MAX_REWARD_RATIO = type(uint16).max; uint256 public constant MAX_LIDO_FEE_RATE_PER_SECOND = 10 ether; constructor(address _lidoLocator) { LIDO_LOCATOR = ILidoLocator(payable(_lidoLocator)); _disableInitializers(); } /// @notice Initializes the contract /// @param _admin Address of the admin /// @param _quarantinePeriod the quarantine period, seconds /// @param _maxRewardRatioBP the max reward ratio, basis points /// @param _maxLidoFeeRatePerSecond the max Lido fee rate per second function initialize( address _admin, uint256 _quarantinePeriod, uint256 _maxRewardRatioBP, uint256 _maxLidoFeeRatePerSecond ) external initializer { if (_admin == address(0)) revert AdminCannotBeZero(); _grantRole(DEFAULT_ADMIN_ROLE, _admin); _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond); } /// @notice returns the latest report data /// @return timestamp of the report /// @return refSlot of the report /// @return treeRoot merkle root of the report /// @return reportCid IPFS CID for the report JSON file function latestReportData() external view returns ( uint256 timestamp, uint256 refSlot, bytes32 treeRoot, string memory reportCid ) { Storage storage $ = _storage(); return ($.vaultsDataTimestamp, $.vaultsDataRefSlot, $.vaultsDataTreeRoot, $.vaultsDataReportCid); } /// @notice returns the latest report timestamp function latestReportTimestamp() external view returns (uint256) { return _storage().vaultsDataTimestamp; } /// @notice returns the quarantine period function quarantinePeriod() external view returns (uint256) { return _storage().quarantinePeriod; } /// @notice returns the max reward ratio for refSlot total value, basis points function maxRewardRatioBP() external view returns (uint256) { return _storage().maxRewardRatioBP; } /// @notice returns the max Lido fee rate per second, in ether function maxLidoFeeRatePerSecond() external view returns (uint256) { return _storage().maxLidoFeeRatePerSecond; } /// @notice returns the amount of total value that is pending in the quarantine for the given vault function quarantineValue(address _vault) external view returns (uint256) { Quarantine memory q = _storage().vaultQuarantines[_vault]; uint256 pendingValue = q.pendingTotalValueIncrease; if (pendingValue > 0) { // saving one SLOAD if pendingValue is zero pendingValue += q.totalValueRemainder; } return pendingValue; } /// @notice returns the quarantine info for the vault /// @param _vault the address of the vault /// @dev returns zeroed structure if there is no active quarantine function vaultQuarantine(address _vault) external view returns (QuarantineInfo memory) { Quarantine memory q = _storage().vaultQuarantines[_vault]; bool isQuarantineInactive = q.pendingTotalValueIncrease == 0; if (isQuarantineInactive) { return QuarantineInfo(false, 0, 0, 0, 0); } return QuarantineInfo({ isActive: true, pendingTotalValueIncrease: q.pendingTotalValueIncrease, startTimestamp: q.startTimestamp, endTimestamp: q.startTimestamp + _storage().quarantinePeriod, totalValueRemainder: q.totalValueRemainder }); } /// @notice returns the number of vaults connected to the VaultHub /// @return the number of vaults connected to the VaultHub function vaultsCount() external view returns (uint256) { return _vaultHub().vaultsCount(); } /// @notice returns batch of vaults info /// @param _offset in the vaults list [0, vaultsCount) /// @param _limit maximum number of vaults to return /// @return batch of vaults info function batchVaultsInfo(uint256 _offset, uint256 _limit) external view returns (VaultInfo[] memory) { VaultHub vaultHub = _vaultHub(); uint256 vaultCount = vaultHub.vaultsCount(); uint256 batchSize; if (_offset > vaultCount) { batchSize = 0; } else { batchSize = _offset + _limit > vaultCount ? vaultCount - _offset : _limit; } VaultInfo[] memory batch = new VaultInfo[](batchSize); for (uint256 i = 0; i < batchSize; i++) { address vaultAddress = vaultHub.vaultByIndex(_offset + i + 1); batch[i] = _vaultInfo(vaultAddress, vaultHub); } return batch; } /// @notice returns the vault data info /// @param _vault the address of the vault /// @return the vault data info function vaultInfo(address _vault) external view returns (VaultInfo memory) { return _vaultInfo(_vault, _vaultHub()); } /** * @notice batch method to mass check the validator statuses in PredepositGuarantee contract * @param _pubkeys the array of validator's pubkeys to check * @return batch array of IPredepositGuarantee.ValidatorStatus structs */ function batchValidatorStatuses( bytes[] calldata _pubkeys ) external view returns (IPredepositGuarantee.ValidatorStatus[] memory batch) { batch = new IPredepositGuarantee.ValidatorStatus[](_pubkeys.length); for (uint256 i = 0; i < _pubkeys.length; i++) { batch[i] = predepositGuarantee().validatorStatus(_pubkeys[i]); } } /// @notice update the sanity parameters /// @param _quarantinePeriod the quarantine period /// @param _maxRewardRatioBP the max EL CL rewards /// @param _maxLidoFeeRatePerSecond the max Lido fee rate per second function updateSanityParams( uint256 _quarantinePeriod, uint256 _maxRewardRatioBP, uint256 _maxLidoFeeRatePerSecond ) external onlyRole(UPDATE_SANITY_PARAMS_ROLE) { _updateSanityParams(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond); } /// @notice Store the report root and its meta information /// @param _vaultsDataTimestamp the timestamp of the report /// @param _vaultsDataRefSlot the refSlot of the report /// @param _vaultsDataTreeRoot the root of the report /// @param _vaultsDataReportCid the CID of the report function updateReportData( uint256 _vaultsDataTimestamp, uint256 _vaultsDataRefSlot, bytes32 _vaultsDataTreeRoot, string memory _vaultsDataReportCid ) external override(ILazyOracle) { if (msg.sender != LIDO_LOCATOR.accountingOracle()) revert NotAuthorized(); Storage storage $ = _storage(); $.vaultsDataTimestamp = uint64(_vaultsDataTimestamp); $.vaultsDataRefSlot = uint48(_vaultsDataRefSlot); $.vaultsDataTreeRoot = _vaultsDataTreeRoot; $.vaultsDataReportCid = _vaultsDataReportCid; emit VaultsReportDataUpdated( _vaultsDataTimestamp, _vaultsDataRefSlot, _vaultsDataTreeRoot, _vaultsDataReportCid ); } /// @notice Permissionless update of the vault data /// @param _vault the address of the vault /// @param _totalValue the total value of the vault /// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether) /// @param _liabilityShares the liabilityShares value of the vault (on the vaultsDataRefSlot) /// @param _maxLiabilityShares the maxLiabilityShares value of the vault (on the vaultsDataRefSlot) /// @param _proof the proof of the reported data function updateVaultData( address _vault, uint256 _totalValue, uint256 _cumulativeLidoFees, uint256 _liabilityShares, uint256 _maxLiabilityShares, uint256 _slashingReserve, bytes32[] calldata _proof ) external { bytes32 leaf = keccak256( bytes.concat( keccak256( abi.encode( _vault, _totalValue, _cumulativeLidoFees, _liabilityShares, _maxLiabilityShares, _slashingReserve ) ) ) ); if (!MerkleProof.verify(_proof, _storage().vaultsDataTreeRoot, leaf)) revert InvalidProof(); uint256 vaultsDataTimestamp = _storage().vaultsDataTimestamp; (uint256 checkedTotalValue, int256 inOutDelta) = _handleSanityChecks( _vault, _totalValue, _storage().vaultsDataRefSlot, vaultsDataTimestamp, _cumulativeLidoFees, _liabilityShares, _maxLiabilityShares ); _vaultHub().applyVaultReport( _vault, vaultsDataTimestamp, checkedTotalValue, inOutDelta, _cumulativeLidoFees, _liabilityShares, _maxLiabilityShares, _slashingReserve ); } /// @notice removes the quarantine for the vault /// @param _vault the address of the vault function removeVaultQuarantine(address _vault) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized(); mapping(address => Quarantine) storage quarantines = _storage().vaultQuarantines; if (quarantines[_vault].pendingTotalValueIncrease > 0) { emit QuarantineRemoved(_vault); } delete quarantines[_vault]; } function _vaultInfo(address _vault, VaultHub _vh) internal view returns (VaultInfo memory) { IStakingVault vault = IStakingVault(_vault); VaultHub.VaultConnection memory connection = _vh.vaultConnection(_vault); VaultHub.VaultRecord memory record = _vh.vaultRecord(_vault); return VaultInfo( _vault, vault.availableBalance() + vault.stagedBalance(), record.inOutDelta.currentValue(), vault.withdrawalCredentials(), record.liabilityShares, record.maxLiabilityShares, _mintableStETH(_vault, _vh), connection.shareLimit, connection.reserveRatioBP, connection.forcedRebalanceThresholdBP, connection.infraFeeBP, connection.liquidityFeeBP, connection.reservationFeeBP, _vh.isPendingDisconnect(_vault) ); } /// @notice handle sanity checks for the vault lazy report data /// @param _vault the address of the vault /// @param _totalValue the total value of the vault in refSlot /// @param _reportRefSlot the refSlot of the report /// @param _reportTimestamp the timestamp of the report /// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether) /// @param _liabilityShares the liabilityShares value of the vault (on the _reportRefSlot) /// @param _maxLiabilityShares the maxLiabilityShares value of the vault (on the _reportRefSlot) /// @return totalValueWithoutQuarantine the smoothed total value of the vault after sanity checks /// @return inOutDeltaOnRefSlot the inOutDelta in the refSlot function _handleSanityChecks( address _vault, uint256 _totalValue, uint256 _reportRefSlot, uint256 _reportTimestamp, uint256 _cumulativeLidoFees, uint256 _liabilityShares, uint256 _maxLiabilityShares ) internal returns (uint256 totalValueWithoutQuarantine, int256 inOutDeltaOnRefSlot) { VaultHub vaultHub = _vaultHub(); VaultHub.VaultRecord memory record = vaultHub.vaultRecord(_vault); uint48 previousReportTs = record.report.timestamp; // 0. Check if the report is already fresh enough if (uint48(_reportTimestamp) <= previousReportTs) { revert VaultReportIsFreshEnough(); } // 1. Calculate inOutDelta in the refSlot int256 currentInOutDelta = record.inOutDelta.currentValue(); inOutDeltaOnRefSlot = record.inOutDelta.getValueForRefSlot(uint48(_reportRefSlot)); // 2. Sanity check for total value increase totalValueWithoutQuarantine = _processTotalValue( _vault, _totalValue, inOutDeltaOnRefSlot, record, _reportTimestamp); // 3. Sanity check for dynamic total value underflow if (int256(totalValueWithoutQuarantine) + currentInOutDelta - inOutDeltaOnRefSlot < 0) { revert UnderflowInTotalValueCalculation(); } // 4. Sanity check for cumulative Lido fees uint256 previousCumulativeLidoFees = record.cumulativeLidoFees; if (previousCumulativeLidoFees > _cumulativeLidoFees) { revert CumulativeLidoFeesTooLow(_cumulativeLidoFees, previousCumulativeLidoFees); } uint256 maxLidoFees = (_reportTimestamp - previousReportTs) * uint256(_storage().maxLidoFeeRatePerSecond); if (_cumulativeLidoFees - previousCumulativeLidoFees > maxLidoFees) { revert CumulativeLidoFeesTooLarge(_cumulativeLidoFees - previousCumulativeLidoFees, maxLidoFees); } // 5. _maxLiabilityShares must be greater or equal than _liabilityShares // _maxLiabilityShares must be less or equal than the currently tracked on-chain record.maxLiabilityShares // (the latter can increase after the ref slot reported) if (_maxLiabilityShares < _liabilityShares || _maxLiabilityShares > record.maxLiabilityShares) { revert InvalidMaxLiabilityShares(); } } /* Quarantine State Diagram States: • NO_QUARANTINE: No active quarantine, all value is immediately available • QUARANTINE_ACTIVE: Total value increase is quarantined, waiting for expiration • QUARANTINE_EXPIRED: Quarantine period passed, quarantined value can be released ┌─────────────────┐ ┌──────────────────┐ │ NO_QUARANTINE │ reported > threshold │QUARANTINE_ACTIVE │ │ ├─────────────────────────────►│ │ │ quarantined=0 │ │ quarantined>0 │ │ startTime=0 │◄─────────────────────────────┤ startTime>0 │ │ | │ time<expiration | └─────────────────┘ reported ≤ threshold └───┬──────────────┘ ▲ (early release) │ ▲ │ │ │ increase > quarantined + rewards │ time ≥ │ │ (release old, start new) │ quarantine period │ │ │ ▼ │ │ ┌─────────────┴────────┐ │ reported ≤ threshold OR │ QUARANTINE_EXPIRED │ │ increase ≤ quarantined + rewards │ │ │ │ quarantined>0 │ │ │ startTime>0 │ └──────────────────────────────────────┤ time>=expiration │ └──────────────────────┘ Legend: • threshold = onchainTotalValue * (100% + maxRewardRatio) • increase = reportedTotalValue - onchainTotalValue • quarantined - total value increase that is currently quarantined • rewards - expected EL/CL rewards based on maxRewardRatio • time = block.timestamp • expiration = quarantine.startTimestamp + quarantinePeriod */ function _processTotalValue( address _vault, uint256 _reportedTotalValue, int256 _inOutDeltaOnRefSlot, VaultHub.VaultRecord memory record, uint256 _reportTimestamp ) internal returns (uint256 totalValueWithoutQuarantine) { if (_reportedTotalValue > MAX_SANE_TOTAL_VALUE) { revert TotalValueTooLarge(); } // Calculate base values for quarantine logic ------------------------- // -------------------------------------------------------------------- // 0. Read storage values Storage storage $ = _storage(); Quarantine storage quarantine = $.vaultQuarantines[_vault]; uint256 quarantinedValue = quarantine.pendingTotalValueIncrease; // 1. Onchain total value on refSlot, it does not include CL difference and EL rewards for the period uint256 onchainTotalValueOnRefSlot = uint256(int256(uint256(record.report.totalValue)) + _inOutDeltaOnRefSlot - record.report.inOutDelta); // 2. Some percentage of funds that haven’t passed through the vault’s balance is allowed for handling EL and CL rewards. // NB: allowed amount of rewards is not scaled by time here, because: // - if we set a small per-day percentage, honest vaults receiving unexpectedly high MEV would get quarantined; // - if we set a large per-day percentage, a vault that hasn’t reported for a long time could bypass quarantine; // As a result, we would need to impose very tiny limits for non-quarantine percentage — which would complicate the logic // without bringing meaningful improvements. uint256 quarantineThreshold = onchainTotalValueOnRefSlot * (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS; // 3. Determine current quarantine state QuarantineState currentState = _determineQuarantineState(quarantine, quarantinedValue, _reportTimestamp); // Execute logic based on current state and conditions ---------------- // -------------------------------------------------------------------- if (currentState == QuarantineState.NO_QUARANTINE) { if (_reportedTotalValue <= quarantineThreshold) { // Transition: NO_QUARANTINE → NO_QUARANTINE (no change needed) return _reportedTotalValue; } else { // Transition: NO_QUARANTINE → QUARANTINE_ACTIVE (start new quarantine) _startNewQuarantine(quarantine, _reportedTotalValue - onchainTotalValueOnRefSlot, _reportTimestamp); emit QuarantineActivated(_vault, _reportedTotalValue - onchainTotalValueOnRefSlot); return onchainTotalValueOnRefSlot; } } else if (currentState == QuarantineState.QUARANTINE_ACTIVE) { if (_reportedTotalValue <= quarantineThreshold) { // Transition: QUARANTINE_ACTIVE → NO_QUARANTINE (release quarantine early) delete $.vaultQuarantines[_vault]; emit QuarantineReleased(_vault, 0); return _reportedTotalValue; } else { // Transition: QUARANTINE_ACTIVE → QUARANTINE_ACTIVE (maintain quarantine) uint256 reminder = _reportedTotalValue > (onchainTotalValueOnRefSlot + quarantinedValue) ? _reportedTotalValue - (onchainTotalValueOnRefSlot + quarantinedValue) : 0; quarantine.totalValueRemainder = uint128(reminder); emit QuarantineUpdated(reminder); return onchainTotalValueOnRefSlot; } } else { // QuarantineState.QUARANTINE_EXPIRED uint256 totalValueIncrease = _reportedTotalValue > onchainTotalValueOnRefSlot ? _reportedTotalValue - onchainTotalValueOnRefSlot : 0; uint256 quarantineThresholdWithRewards = quarantineThreshold + quarantinedValue * (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS; if (_reportedTotalValue <= quarantineThresholdWithRewards) { // Transition: QUARANTINE_EXPIRED → NO_QUARANTINE (release and accept all) delete $.vaultQuarantines[_vault]; emit QuarantineReleased(_vault, _reportedTotalValue <= quarantineThreshold ? 0 : totalValueIncrease); return _reportedTotalValue; } else { // Transition: QUARANTINE_EXPIRED → QUARANTINE_ACTIVE (release old, start new) emit QuarantineReleased(_vault, quarantinedValue); _startNewQuarantine(quarantine, totalValueIncrease - quarantinedValue, _reportTimestamp); emit QuarantineActivated(_vault, totalValueIncrease - quarantinedValue); return onchainTotalValueOnRefSlot + quarantinedValue; } } } function _determineQuarantineState( Quarantine storage _quarantine, uint256 _quarantinedValue, uint256 _vaultsDataTimestamp ) internal view returns (QuarantineState) { if (_quarantinedValue == 0) { return QuarantineState.NO_QUARANTINE; } bool isQuarantineExpired = (_vaultsDataTimestamp - _quarantine.startTimestamp) >= _storage().quarantinePeriod; return isQuarantineExpired ? QuarantineState.QUARANTINE_EXPIRED : QuarantineState.QUARANTINE_ACTIVE; } function _startNewQuarantine( Quarantine storage _quarantine, uint256 _amountToQuarantine, uint256 _currentTimestamp ) internal { _quarantine.pendingTotalValueIncrease = uint128(_amountToQuarantine); _quarantine.startTimestamp = uint64(_currentTimestamp); _quarantine.totalValueRemainder = 0; } function _updateSanityParams( uint256 _quarantinePeriod, uint256 _maxRewardRatioBP, uint256 _maxLidoFeeRatePerSecond ) internal { if (_quarantinePeriod > MAX_QUARANTINE_PERIOD) { revert QuarantinePeriodTooLarge(_quarantinePeriod, MAX_QUARANTINE_PERIOD); } if (_maxRewardRatioBP > MAX_REWARD_RATIO) { revert MaxRewardRatioTooLarge(_maxRewardRatioBP, MAX_REWARD_RATIO); } if (_maxLidoFeeRatePerSecond > MAX_LIDO_FEE_RATE_PER_SECOND) { revert MaxLidoFeeRatePerSecondTooLarge(_maxLidoFeeRatePerSecond, MAX_LIDO_FEE_RATE_PER_SECOND); } Storage storage $ = _storage(); $.quarantinePeriod = uint64(_quarantinePeriod); $.maxRewardRatioBP = uint16(_maxRewardRatioBP); $.maxLidoFeeRatePerSecond = uint64(_maxLidoFeeRatePerSecond); emit SanityParamsUpdated(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond); } function _mintableStETH(address _vault, VaultHub _vh) internal view returns (uint256) { uint256 mintableShares = _vh.totalMintingCapacityShares(_vault, 0 /* zero eth delta */); return _getPooledEthBySharesRoundUp(mintableShares); } function _storage() internal pure returns (Storage storage $) { assembly { $.slot := LAZY_ORACLE_STORAGE_LOCATION } } function predepositGuarantee() internal view returns (IPredepositGuarantee) { return IPredepositGuarantee(LIDO_LOCATOR.predepositGuarantee()); } function _vaultHub() internal view returns (VaultHub) { return VaultHub(payable(LIDO_LOCATOR.vaultHub())); } function _operatorGrid() internal view returns (OperatorGrid) { return OperatorGrid(LIDO_LOCATOR.operatorGrid()); } function _getPooledEthBySharesRoundUp(uint256 _shares) internal view returns (uint256) { return ILido(LIDO_LOCATOR.lido()).getPooledEthBySharesRoundUp(_shares); } event VaultsReportDataUpdated(uint256 indexed timestamp, uint256 indexed refSlot, bytes32 indexed root, string cid); event QuarantineActivated(address indexed vault, uint256 delta); event QuarantineReleased(address indexed vault, uint256 delta); event QuarantineRemoved(address indexed vault); event QuarantineUpdated(uint256 totalValueReminder); event SanityParamsUpdated(uint256 quarantinePeriod, uint256 maxRewardRatioBP, uint256 maxLidoFeeRatePerSecond); error AdminCannotBeZero(); error NotAuthorized(); error InvalidProof(); error UnderflowInTotalValueCalculation(); error TotalValueTooLarge(); error VaultReportIsFreshEnough(); error CumulativeLidoFeesTooLow(uint256 reportingFees, uint256 previousFees); error CumulativeLidoFeesTooLarge(uint256 feeIncrease, uint256 maxFeeIncrease); error QuarantinePeriodTooLarge(uint256 quarantinePeriod, uint256 maxQuarantinePeriod); error MaxRewardRatioTooLarge(uint256 rewardRatio, uint256 maxRewardRatio); error MaxLidoFeeRatePerSecondTooLarge(uint256 feeRate, uint256 maxFeeRate); error InvalidMaxLiabilityShares(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {StorageSlot} from "@openzeppelin/contracts-v5.2/utils/StorageSlot.sol"; import {IBeacon} from "@openzeppelin/contracts-v5.2/proxy/beacon/IBeacon.sol"; import {ERC1967Utils} from "@openzeppelin/contracts-v5.2/proxy/ERC1967/ERC1967Utils.sol"; library PinnedBeaconUtils { /** * @dev Storage slot with the address of the last implementation. * PINNED_BEACON_STORAGE_SLOT = bytes32(uint256(keccak256("stakingVault.proxy.pinnedBeacon")) - 1) */ bytes32 internal constant PINNED_BEACON_STORAGE_SLOT = 0x8d75cfa6c9a3cd2fb8b6d445eafb32adc5497a45b333009f9000379f7024f9f5; function getPinnedImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(PINNED_BEACON_STORAGE_SLOT).value; } /** * @notice Ossifies the beacon by pinning the current implementation */ function ossify() internal { if (getPinnedImplementation() != address(0)) revert AlreadyOssified(); address currentImplementation = IBeacon(ERC1967Utils.getBeacon()).implementation(); StorageSlot.getAddressSlot(PINNED_BEACON_STORAGE_SLOT).value = currentImplementation; emit PinnedImplementationUpdated(currentImplementation); } /** * @notice Emitted when the pinned implementation is updated * @param implementation The address of the new pinned implementation */ event PinnedImplementationUpdated(address indexed implementation); /** * @notice Thrown when trying to ossify the proxy while it is already ossified */ error AlreadyOssified(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {SafeERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts-v5.2/token/ERC20/IERC20.sol"; library RecoverTokens { /** * @notice ETH address convention per EIP-7528 */ address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /** * @notice Emitted when the ERC20 `token` or ether is recovered (i.e. transferred) * @param to The address of the recovery recipient * @param assetAddress The address of the recovered ERC20 token (0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for ether) * @param amount The amount of the token recovered */ event AssetsRecovered(address indexed to, address indexed assetAddress, uint256 amount); /** * @notice Error thrown when recovery of ETH fails on transfer to recipient * @param recipient Address of the recovery recipient * @param amount Amount of ETH attempted to recover */ error EthTransferFailed(address recipient, uint256 amount); function _recoverEth( address _recipient, uint256 _amount ) internal { (bool success,) = payable(_recipient).call{value: _amount}(""); if (!success) revert EthTransferFailed(_recipient, _amount); emit AssetsRecovered(_recipient, ETH, _amount); } function _recoverERC20( address _token, address _recipient, uint256 _amount ) internal { SafeERC20.safeTransfer(IERC20(_token), _recipient, _amount); emit AssetsRecovered(_recipient, _token, _amount); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable one-contract-per-file pragma solidity 0.8.25; import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol"; uint256 constant DOUBLE_CACHE_LENGTH = 2; // wrap external call in function to save bytecode function _getCurrentRefSlot(IHashConsensus _consensus) view returns (uint256) { (uint256 refSlot, ) = _consensus.getCurrentFrame(); return refSlot; } library RefSlotCache { struct Uint104WithCache { uint104 value; uint104 valueOnRefSlot; uint48 refSlot; } /// @notice Increases the value and caches the previous value for the current refSlot /// @param _storage The storage slot to update /// @param _consensus The consensus contract to get the current refSlot /// @param _increment increment the value by this amount /// @return the updated struct to be saved in storage function withValueIncrease( Uint104WithCache storage _storage, IHashConsensus _consensus, uint104 _increment ) internal view returns (Uint104WithCache memory) { uint256 refSlot = _getCurrentRefSlot(_consensus); Uint104WithCache memory newCache = _storage; if (newCache.refSlot != uint48(refSlot)) { newCache.valueOnRefSlot = _storage.value; newCache.refSlot = uint48(refSlot); } newCache.value += _increment; return newCache; } /// @notice Returns the value for the current refSlot /// @param _storage the storage pointer for the cached value /// @param _consensus the consensus contract to get the current refSlot /// @return the cached value if it's changed since the last refSlot, the current value otherwise function getValueForLastRefSlot( Uint104WithCache storage _storage, IHashConsensus _consensus ) internal view returns (uint104) { uint256 refSlot = _getCurrentRefSlot(_consensus); if (uint48(refSlot) != _storage.refSlot) { return _storage.value; } else { return _storage.valueOnRefSlot; } } } library DoubleRefSlotCache { struct Int104WithCache { int104 value; int104 valueOnRefSlot; uint48 refSlot; } /// @notice Initializes the cache with the given value /// @param _value the value to initialize the cache with /// @return the initialized cache function initializeInt104DoubleCache( int104 _value ) internal pure returns (Int104WithCache[DOUBLE_CACHE_LENGTH] memory) { return [ Int104WithCache({ value: _value, valueOnRefSlot: 0, refSlot: 0 // first cache slot is active by default (as >= used in _activeCacheIndex) }), Int104WithCache(0, 0, 0) ]; } /// @notice Increases the value and caches the previous value for the current refSlot /// @param _storage The storage slot to update /// @param _consensus The consensus contract to get the current refSlot /// @param _increment increment the value by this amount /// @return the updated struct to be saved in storage function withValueIncrease( Int104WithCache[DOUBLE_CACHE_LENGTH] storage _storage, IHashConsensus _consensus, int104 _increment ) internal view returns (Int104WithCache[DOUBLE_CACHE_LENGTH] memory) { uint256 refSlot = _getCurrentRefSlot(_consensus); Int104WithCache[DOUBLE_CACHE_LENGTH] memory newCache = _storage; uint256 activeCacheIndex = _activeCacheIndex(newCache); if (newCache[activeCacheIndex].refSlot != uint48(refSlot)) { uint256 previousCacheIndex = activeCacheIndex; activeCacheIndex = 1 - activeCacheIndex; newCache[activeCacheIndex].value = newCache[previousCacheIndex].value; newCache[activeCacheIndex].valueOnRefSlot = newCache[previousCacheIndex].value; newCache[activeCacheIndex].refSlot = uint48(refSlot); } newCache[activeCacheIndex].value += _increment; return newCache; } /// @notice Returns the current value of the cache /// @param _cache the storage pointer for the array of cached values /// @return the current value of the cache function currentValue(Int104WithCache[DOUBLE_CACHE_LENGTH] memory _cache) internal pure returns (int104) { return _cache[_activeCacheIndex(_cache)].value; } /// @notice Returns the value for the refSlot /// @param _cache the storage pointer for the cached value /// @param _refSlot the refSlot to get the value for /// @return the cached value if it's changed since the last refSlot, the current value otherwise /// @dev reverts if the cache was overwritten after target refSlot function getValueForRefSlot( Int104WithCache[DOUBLE_CACHE_LENGTH] memory _cache, uint48 _refSlot ) internal pure returns (int104) { uint256 activeCacheIndex = _activeCacheIndex(_cache); // 1. refSlot is more than activeRefSlot if (_refSlot > _cache[activeCacheIndex].refSlot) { return _cache[activeCacheIndex].value; } uint256 previousCacheIndex = 1 - activeCacheIndex; // 2. refSlot is in (prevRefSlot, activeRefSlot] if (_refSlot > _cache[previousCacheIndex].refSlot) { return _cache[activeCacheIndex].valueOnRefSlot; } // 3. refSlot is equal to prevRefSlot if (_refSlot == _cache[previousCacheIndex].refSlot) { return _cache[previousCacheIndex].valueOnRefSlot; } // 4. refSlot is less than prevRefSlot revert InOutDeltaCacheIsOverwritten(); } /// @dev There is a limitation on the refSlot value: it must be less than 2^48. /// If it exceeds this limit, the refSlot will be truncated to 48 bits. /// _activeCacheIndex may work incorrectly if one refSlot value is truncated and the other is not, /// because the non-truncated value will always be greater than the truncated one, /// causing incorrect activeIndex determination. However, 2^48 is a very large number, /// so if block time will be 1 second, it will take 8_925_512 years to reach this limit. function _activeCacheIndex(Int104WithCache[DOUBLE_CACHE_LENGTH] memory _cache) private pure returns (uint256) { return _cache[0].refSlot >= _cache[1].refSlot ? 0 : 1; } error InOutDeltaCacheIsOverwritten(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {SafeCast} from "@openzeppelin/contracts-v5.2/utils/math/SafeCast.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {Confirmable2Addresses} from "../utils/Confirmable2Addresses.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {VaultHub} from "./VaultHub.sol"; struct TierParams { uint256 shareLimit; uint256 reserveRatioBP; uint256 forcedRebalanceThresholdBP; uint256 infraFeeBP; uint256 liquidityFeeBP; uint256 reservationFeeBP; } /** * @title OperatorGrid * @author loga4 * @notice * OperatorGrid is a contract that manages mint parameters for vaults when they are connected to the VaultHub. * These parameters include: * - shareLimit: maximum amount of shares that can be minted * - reserveRatioBP: reserve ratio in basis points * - forcedRebalanceThresholdBP: forced rebalance threshold in basis points * - infraFeeBP: infra fee in basis points * - liquidityFeeBP: liquidity fee in basis points * - reservationFeeBP: reservation fee in basis points * * These parameters are determined by the Tier in which the Vault is registered. * */ contract OperatorGrid is AccessControlEnumerableUpgradeable, Confirmable2Addresses { /* Key concepts: 1. Default Registration: - All Vaults initially have default tier (DEFAULT_TIER_ID = 0) - The default tier has no group DEFAULT_TIER_ID = 0 ┌──────────────────────┐ │ Tier 1 │ │ tierShareLimit = z │ │ Vault_1 ... Vault_m │ └──────────────────────┘ 2. Tier Change Process: - To predefine vaults tier or modify the existing vault's connection parameters to VaultHub, a tier change must be requested - Both vault owner and node operator must confirm the change (doesn't matter who confirms first) - The confirmation has an expiry time (default 1 hour) 3. Tier Reset: - When a vault is disconnected from VaultHub, its tier is automatically reset to the default tier (DEFAULT_TIER_ID) 4. Tier Capacity: - Tiers are not limited by the number of vaults - Tiers are limited by the sum of vaults' liability shares - Administrative operations (like bad debt socialization) can bypass tier/group limits ┌──────────────────────────────────────────────────────┐ │ Group 1 = operator 1 │ │ ┌────────────────────────────────────────────────┐ │ │ │ groupShareLimit = 1kk │ │ │ └────────────────────────────────────────────────┘ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ Tier 1 │ │ Tier 2 │ │ │ │ tierShareLimit = x │ │ tierShareLimit = y │ │ │ │ Vault_2 ... Vault_k │ │ │ │ │ └──────────────────────┘ └──────────────────────┘ │ └──────────────────────────────────────────────────────┘ 5. Jail Mechanism: - A vault can be "jailed" as a penalty mechanism for misbehavior or violations - When a vault is in jail, it cannot mint new stETH shares (normal minting operations are blocked) - Vaults can be jailed/unjailed by addresses with appropriate governance roles - Administrative operations (like bad debt socialization) can bypass jail restrictions */ /// @dev 0xa495a3428837724c7f7648cda02eb83c9c4c778c8688d6f254c7f3f80c154d55 bytes32 public constant REGISTRY_ROLE = keccak256("vaults.OperatorsGrid.Registry"); /// @notice Lido Locator contract ILidoLocator public immutable LIDO_LOCATOR; uint256 public constant DEFAULT_TIER_ID = 0; // Special address to denote that default tier is not linked to any real operator address public constant DEFAULT_TIER_OPERATOR = address(uint160(type(uint160).max)); /// @dev basis points base uint256 internal constant TOTAL_BASIS_POINTS = 100_00; /// @dev max value for fees in basis points - it's about 650% uint256 internal constant MAX_FEE_BP = type(uint16).max; /// @dev max value for reserve ratio in basis points - 9999 uint256 internal constant MAX_RESERVE_RATIO_BP = 99_99; // ----------------------------- // STRUCTS // ----------------------------- struct Group { address operator; uint96 shareLimit; uint96 liabilityShares; uint256[] tierIds; } struct Tier { address operator; uint96 shareLimit; uint96 liabilityShares; uint16 reserveRatioBP; uint16 forcedRebalanceThresholdBP; uint16 infraFeeBP; uint16 liquidityFeeBP; uint16 reservationFeeBP; } /** * @notice ERC-7201 storage namespace for the OperatorGrid * @dev ERC-7201 namespace is used to prevent upgrade collisions * @custom:storage-location erc7201:Lido.Vaults.OperatorGrid * @custom:tiers Tiers * @custom:vaultTier Vault tier * @custom:groups Groups * @custom:nodeOperators Node operators * @custom:isVaultInJail if true, vault is in jail and can't mint stETH */ struct ERC7201Storage { Tier[] tiers; mapping(address vault => uint256 tierId) vaultTier; mapping(address nodeOperator => Group) groups; address[] nodeOperators; mapping(address vault => bool isInJail) isVaultInJail; } /** * @notice Storage offset slot for ERC-7201 namespace * The storage namespace is used to prevent upgrade collisions * keccak256(abi.encode(uint256(keccak256("Lido.Vaults.OperatorGrid")) - 1)) & ~bytes32(uint256(0xff)) */ bytes32 private constant OPERATOR_GRID_STORAGE_LOCATION = 0x6b64617c951381e2c1eff2be939fe368ab6d76b7d335df2e47ba2309eba1c700; /// @notice Initializes the contract with a LidoLocator /// @param _locator LidoLocator contract constructor(ILidoLocator _locator) { LIDO_LOCATOR = _locator; _disableInitializers(); } /// @notice Initializes the contract with an admin /// @param _admin Address of the admin /// @param _defaultTierParams Default tier params for the default tier function initialize(address _admin, TierParams calldata _defaultTierParams) external initializer { if (_admin == address(0)) revert ZeroArgument("_admin"); __AccessControlEnumerable_init(); __Confirmations_init(); _grantRole(DEFAULT_ADMIN_ROLE, _admin); _validateParams( DEFAULT_TIER_ID, _defaultTierParams.reserveRatioBP, _defaultTierParams.forcedRebalanceThresholdBP, _defaultTierParams.infraFeeBP, _defaultTierParams.liquidityFeeBP, _defaultTierParams.reservationFeeBP ); ERC7201Storage storage $ = _getStorage(); //create default tier with default share limit $.tiers.push( Tier({ operator: DEFAULT_TIER_OPERATOR, shareLimit: SafeCast.toUint96(_defaultTierParams.shareLimit), reserveRatioBP: uint16(_defaultTierParams.reserveRatioBP), forcedRebalanceThresholdBP: uint16(_defaultTierParams.forcedRebalanceThresholdBP), infraFeeBP: uint16(_defaultTierParams.infraFeeBP), liquidityFeeBP: uint16(_defaultTierParams.liquidityFeeBP), reservationFeeBP: uint16(_defaultTierParams.reservationFeeBP), liabilityShares: 0 }) ); } /// @notice Sets the confirmation expiry period /// @param _newConfirmExpiry The new confirmation expiry period in seconds function setConfirmExpiry(uint256 _newConfirmExpiry) external onlyRole(REGISTRY_ROLE) { _setConfirmExpiry(_newConfirmExpiry); } /// @notice Registers a new group /// @param _nodeOperator address of the node operator /// @param _shareLimit Maximum share limit for the group function registerGroup(address _nodeOperator, uint256 _shareLimit) external onlyRole(REGISTRY_ROLE) { if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator"); ERC7201Storage storage $ = _getStorage(); if ($.groups[_nodeOperator].operator != address(0)) revert GroupExists(); $.groups[_nodeOperator] = Group({ operator: _nodeOperator, shareLimit: SafeCast.toUint96(_shareLimit), liabilityShares: 0, tierIds: new uint256[](0) }); $.nodeOperators.push(_nodeOperator); emit GroupAdded(_nodeOperator, _shareLimit); } /// @notice Updates the share limit of a group /// @param _nodeOperator address of the node operator /// @param _shareLimit New share limit value function updateGroupShareLimit(address _nodeOperator, uint256 _shareLimit) external onlyRole(REGISTRY_ROLE) { if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator"); ERC7201Storage storage $ = _getStorage(); Group storage group_ = $.groups[_nodeOperator]; if (group_.operator == address(0)) revert GroupNotExists(); group_.shareLimit = SafeCast.toUint96(_shareLimit); emit GroupShareLimitUpdated(_nodeOperator, _shareLimit); } /// @notice Returns a group by node operator address /// @param _nodeOperator address of the node operator /// @return Group function group(address _nodeOperator) external view returns (Group memory) { return _getStorage().groups[_nodeOperator]; } /// @notice Returns a node operator address by index /// @param _index index of the node operator /// @return Node operator address function nodeOperatorAddress(uint256 _index) external view returns (address) { ERC7201Storage storage $ = _getStorage(); if (_index >= $.nodeOperators.length) revert NodeOperatorNotExists(); return $.nodeOperators[_index]; } /// @notice Returns a node operator count /// @return Node operator count function nodeOperatorCount() external view returns (uint256) { return _getStorage().nodeOperators.length; } /// @notice Registers a new tier /// @param _nodeOperator address of the node operator /// @param _tiers array of tiers to register function registerTiers( address _nodeOperator, TierParams[] calldata _tiers ) external onlyRole(REGISTRY_ROLE) { if (_nodeOperator == address(0)) revert ZeroArgument("_nodeOperator"); ERC7201Storage storage $ = _getStorage(); Group storage group_ = $.groups[_nodeOperator]; if (group_.operator == address(0)) revert GroupNotExists(); uint256 tierId = $.tiers.length; uint256 length = _tiers.length; for (uint256 i = 0; i < length; i++) { _validateParams( tierId, _tiers[i].reserveRatioBP, _tiers[i].forcedRebalanceThresholdBP, _tiers[i].infraFeeBP, _tiers[i].liquidityFeeBP, _tiers[i].reservationFeeBP ); Tier memory tier_ = Tier({ operator: _nodeOperator, shareLimit: SafeCast.toUint96(_tiers[i].shareLimit), reserveRatioBP: uint16(_tiers[i].reserveRatioBP), forcedRebalanceThresholdBP: uint16(_tiers[i].forcedRebalanceThresholdBP), infraFeeBP: uint16(_tiers[i].infraFeeBP), liquidityFeeBP: uint16(_tiers[i].liquidityFeeBP), reservationFeeBP: uint16(_tiers[i].reservationFeeBP), liabilityShares: 0 }); $.tiers.push(tier_); group_.tierIds.push(tierId); emit TierAdded( _nodeOperator, tierId, tier_.shareLimit, tier_.reserveRatioBP, tier_.forcedRebalanceThresholdBP, tier_.infraFeeBP, tier_.liquidityFeeBP, tier_.reservationFeeBP ); tierId++; } } /// @notice Returns a tier by ID /// @param _tierId id of the tier /// @return Tier function tier(uint256 _tierId) external view returns (Tier memory) { ERC7201Storage storage $ = _getStorage(); if (_tierId >= $.tiers.length) revert TierNotExists(); return $.tiers[_tierId]; } /// @notice Returns a tiers count /// @return Tiers count function tiersCount() external view returns (uint256) { return _getStorage().tiers.length; } /// @notice Alters multiple tiers /// @dev We do not enforce to update old vaults with the new tier params, only new ones. /// @param _tierIds array of tier ids to alter /// @param _tierParams array of new tier params function alterTiers( uint256[] calldata _tierIds, TierParams[] calldata _tierParams ) external onlyRole(REGISTRY_ROLE) { if (_tierIds.length != _tierParams.length) revert ArrayLengthMismatch(); ERC7201Storage storage $ = _getStorage(); uint256 length = _tierIds.length; uint256 tiersLength = $.tiers.length; for (uint256 i = 0; i < length; i++) { if (_tierIds[i] >= tiersLength) revert TierNotExists(); _validateParams( _tierIds[i], _tierParams[i].reserveRatioBP, _tierParams[i].forcedRebalanceThresholdBP, _tierParams[i].infraFeeBP, _tierParams[i].liquidityFeeBP, _tierParams[i].reservationFeeBP ); Tier storage tier_ = $.tiers[_tierIds[i]]; tier_.shareLimit = SafeCast.toUint96(_tierParams[i].shareLimit); tier_.reserveRatioBP = uint16(_tierParams[i].reserveRatioBP); tier_.forcedRebalanceThresholdBP = uint16(_tierParams[i].forcedRebalanceThresholdBP); tier_.infraFeeBP = uint16(_tierParams[i].infraFeeBP); tier_.liquidityFeeBP = uint16(_tierParams[i].liquidityFeeBP); tier_.reservationFeeBP = uint16(_tierParams[i].reservationFeeBP); emit TierUpdated( _tierIds[i], tier_.shareLimit, tier_.reserveRatioBP, tier_.forcedRebalanceThresholdBP, tier_.infraFeeBP, tier_.liquidityFeeBP, tier_.reservationFeeBP ); } } /* Legend: V = Vault1.liabilityShares LS = liabilityShares Scheme1 - transfer Vault from default tier to Tier2 ┌──────────────────────────────┐ │ Group 1 │ │ │ ┌────────────────────┐ │ ┌─────────┐ ┌───────────┐ │ │ Tier 1 (default) │ confirm │ │ Tier 2 │ │ Tier 3 │ │ │ LS: -V │ ─────> │ │ LS:+V │ │ │ │ └────────────────────┘ │ └─────────┘ └───────────┘ │ │ │ │ Group1.liabilityShares: +V │ └──────────────────────────────┘ After confirmation: - Tier 1.liabilityShares = -V - Tier 2.liabilityShares = +V - Group1.liabilityShares = +V -------------------------------------------------------------------------- Scheme2 - transfer Vault from Tier2 to Tier3, no need to change group minted shares ┌────────────────────────────────┐ ┌────────────────────────────────┐ │ Group 1 │ │ Group 2 │ │ │ │ │ │ ┌───────────┐ ┌───────────┐ │ │ ┌───────────┐ │ │ │ Tier 2 │ │ Tier 3 │ │ │ │ Tier 4 │ │ │ │ LS:-V │ │ LS:+V │ │ │ │ │ │ │ └───────────┘ └───────────┘ │ │ └───────────┘ │ │ operator1 │ │ operator2 │ └────────────────────────────────┘ └────────────────────────────────┘ After confirmation: - Tier 2.liabilityShares = -V - Tier 3.liabilityShares = +V NB: Cannot change from Tier2 to Tier1, because Tier1 has no group NB: Cannot change from Tier2 to Tier4, because Tier4 has different operator. */ /// @notice Vault tier change with multi-role confirmation /// @param _vault address of the vault /// @param _requestedTierId id of the tier /// @param _requestedShareLimit share limit to set /// @return bool Whether the tier change was executed. /// @dev Node operator confirmation can be collected even if the vault is disconnected /// @dev Requires vault to be connected to VaultHub to finalize tier change from the vault owner side. /// @dev Both vault owner (via Dashboard) and node operator confirmations are required. function changeTier( address _vault, uint256 _requestedTierId, uint256 _requestedShareLimit ) external returns (bool) { if (_vault == address(0)) revert ZeroArgument("_vault"); ERC7201Storage storage $ = _getStorage(); if (_requestedTierId >= $.tiers.length) revert TierNotExists(); if (_requestedTierId == DEFAULT_TIER_ID) revert CannotChangeToDefaultTier(); VaultHub vaultHub = _vaultHub(); uint256 vaultTierId = $.vaultTier[_vault]; if (vaultTierId == _requestedTierId) revert TierAlreadySet(); address nodeOperator = IStakingVault(_vault).nodeOperator(); // we allow node operator to pre-approve not connected vaults if (msg.sender != nodeOperator && !vaultHub.isVaultConnected(_vault)) revert VaultNotConnected(); Tier storage requestedTier = $.tiers[_requestedTierId]; if (nodeOperator != requestedTier.operator) revert TierNotInOperatorGroup(); if (_requestedShareLimit > requestedTier.shareLimit) { revert RequestedShareLimitTooHigh(_requestedShareLimit, requestedTier.shareLimit); } uint256 vaultLiabilityShares = vaultHub.liabilityShares(_vault); if (_requestedShareLimit < vaultLiabilityShares) { revert RequestedShareLimitTooLow(_requestedShareLimit, vaultLiabilityShares); } address vaultOwner = vaultHub.vaultConnection(_vault).owner; // store the caller's confirmation; only proceed if the required number of confirmations is met. if (!_collectAndCheckConfirmations(msg.data, vaultOwner, nodeOperator)) return false; // check if tier limit is exceeded if (requestedTier.liabilityShares + vaultLiabilityShares > requestedTier.shareLimit) revert TierLimitExceeded(); // if the vault was in the default tier: // - that mean that the vault has no group, so we decrease only the minted shares of the default tier // - but need to check requested group limit exceeded if (vaultTierId == DEFAULT_TIER_ID) { Group storage requestedGroup = $.groups[nodeOperator]; if (requestedGroup.liabilityShares + vaultLiabilityShares > requestedGroup.shareLimit) { revert GroupLimitExceeded(); } requestedGroup.liabilityShares += uint96(vaultLiabilityShares); } Tier storage currentTier = $.tiers[vaultTierId]; currentTier.liabilityShares -= uint96(vaultLiabilityShares); requestedTier.liabilityShares += uint96(vaultLiabilityShares); $.vaultTier[_vault] = _requestedTierId; vaultHub.updateConnection( _vault, _requestedShareLimit, requestedTier.reserveRatioBP, requestedTier.forcedRebalanceThresholdBP, requestedTier.infraFeeBP, requestedTier.liquidityFeeBP, requestedTier.reservationFeeBP ); emit TierChanged(_vault, _requestedTierId, _requestedShareLimit); return true; } /// @notice Syncs vault tier with current tier params /// @param _vault address of the vault /// @return bool Whether the sync was executed. /// @dev Requires vault to be connected to VaultHub. /// @dev Both vault owner (via Dashboard) and node operator confirmations are required. function syncTier(address _vault) external returns (bool) { (VaultHub vaultHub, VaultHub.VaultConnection memory vaultConnection, address vaultOwner, address nodeOperator, uint256 vaultTierId) = _getVaultContextForConnectedVault(_vault); Tier storage tier_ = _getStorage().tiers[vaultTierId]; if ( vaultConnection.reserveRatioBP == tier_.reserveRatioBP && vaultConnection.forcedRebalanceThresholdBP == tier_.forcedRebalanceThresholdBP && vaultConnection.infraFeeBP == tier_.infraFeeBP && vaultConnection.liquidityFeeBP == tier_.liquidityFeeBP && vaultConnection.reservationFeeBP == tier_.reservationFeeBP ) { revert VaultAlreadySyncedWithTier(); } // store the caller's confirmation; only proceed if the required number of confirmations is met. if (!_collectAndCheckConfirmations(msg.data, vaultOwner, nodeOperator)) return false; vaultHub.updateConnection( _vault, vaultConnection.shareLimit, tier_.reserveRatioBP, tier_.forcedRebalanceThresholdBP, tier_.infraFeeBP, tier_.liquidityFeeBP, tier_.reservationFeeBP ); return true; } /// @notice Update vault share limit /// @param _vault address of the vault /// @param _requestedShareLimit share limit to set /// @return bool Whether the update was executed. /// @dev Requires vault to be connected to VaultHub. /// @dev Both vault owner (via Dashboard) and node operator confirmations are required. function updateVaultShareLimit(address _vault, uint256 _requestedShareLimit) external returns (bool) { (VaultHub vaultHub, VaultHub.VaultConnection memory vaultConnection, address vaultOwner, address nodeOperator, uint256 vaultTierId) = _getVaultContextForConnectedVault(_vault); uint256 tierShareLimit = _getStorage().tiers[vaultTierId].shareLimit; if (_requestedShareLimit > tierShareLimit) revert RequestedShareLimitTooHigh(_requestedShareLimit, tierShareLimit); if (_requestedShareLimit == vaultConnection.shareLimit) revert ShareLimitAlreadySet(); uint256 vaultLiabilityShares = vaultHub.liabilityShares(_vault); if (_requestedShareLimit < vaultLiabilityShares) { revert RequestedShareLimitTooLow(_requestedShareLimit, vaultLiabilityShares); } // store the caller's confirmation; only proceed if the required number of confirmations is met. if (!_collectAndCheckConfirmations(msg.data, vaultOwner, nodeOperator)) return false; vaultHub.updateConnection( _vault, _requestedShareLimit, vaultConnection.reserveRatioBP, vaultConnection.forcedRebalanceThresholdBP, vaultConnection.infraFeeBP, vaultConnection.liquidityFeeBP, vaultConnection.reservationFeeBP ); return true; } /// @notice Reset vault's tier to default /// @param _vault address of the vault /// @dev Requires vault's liabilityShares to be zero before resetting the tier function resetVaultTier(address _vault) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("resetVaultTier", msg.sender); ERC7201Storage storage $ = _getStorage(); if ($.vaultTier[_vault] != DEFAULT_TIER_ID) { $.vaultTier[_vault] = DEFAULT_TIER_ID; emit TierChanged(_vault, DEFAULT_TIER_ID, $.tiers[DEFAULT_TIER_ID].shareLimit); } } /// @notice updates fees for the vault /// @param _vault vault address /// @param _infraFeeBP new infra fee in basis points /// @param _liquidityFeeBP new liquidity fee in basis points /// @param _reservationFeeBP new reservation fee in basis points function updateVaultFees( address _vault, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) external onlyRole(REGISTRY_ROLE) { if (_vault == address(0)) revert ZeroArgument("_vault"); _requireLessOrEqToBP(_infraFeeBP, MAX_FEE_BP); _requireLessOrEqToBP(_liquidityFeeBP, MAX_FEE_BP); _requireLessOrEqToBP(_reservationFeeBP, MAX_FEE_BP); VaultHub vaultHub = _vaultHub(); if (!vaultHub.isVaultConnected(_vault)) revert VaultNotConnected(); VaultHub.VaultConnection memory vaultConnection = vaultHub.vaultConnection(_vault); vaultHub.updateConnection( _vault, vaultConnection.shareLimit, vaultConnection.reserveRatioBP, vaultConnection.forcedRebalanceThresholdBP, _infraFeeBP, _liquidityFeeBP, _reservationFeeBP ); } // ----------------------------- // MINT / BURN // ----------------------------- /// @notice Mint shares limit check /// @param _vault address of the vault /// @param _amount amount of shares will be minted /// @param _overrideLimits true if group and tier limits should not be checked function onMintedShares( address _vault, uint256 _amount, bool _overrideLimits ) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("onMintedShares", msg.sender); ERC7201Storage storage $ = _getStorage(); if (!_overrideLimits && $.isVaultInJail[_vault]) revert VaultInJail(); uint256 tierId = $.vaultTier[_vault]; Tier storage tier_ = $.tiers[tierId]; uint96 tierLiabilityShares = tier_.liabilityShares; if (!_overrideLimits && tierLiabilityShares + _amount > tier_.shareLimit) { revert TierLimitExceeded(); } tier_.liabilityShares = tierLiabilityShares + uint96(_amount); if (tierId != DEFAULT_TIER_ID) { Group storage group_ = $.groups[tier_.operator]; uint96 groupMintedShares = group_.liabilityShares; if (!_overrideLimits && groupMintedShares + _amount > group_.shareLimit) { revert GroupLimitExceeded(); } group_.liabilityShares = groupMintedShares + uint96(_amount); } } /// @notice Burn shares limit check /// @param _vault address of the vault /// @param _amount amount of shares to burn function onBurnedShares( address _vault, uint256 _amount ) external { if (msg.sender != LIDO_LOCATOR.vaultHub()) revert NotAuthorized("burnShares", msg.sender); ERC7201Storage storage $ = _getStorage(); uint256 tierId = $.vaultTier[_vault]; Tier storage tier_ = $.tiers[tierId]; // we skip the check for minted shared underflow, because it's done in the VaultHub.burnShares() tier_.liabilityShares -= uint96(_amount); if (tierId != DEFAULT_TIER_ID) { Group storage group_ = $.groups[tier_.operator]; group_.liabilityShares -= uint96(_amount); } } /// @notice Updates if the vault is in jail /// @param _vault vault address /// @param _isInJail true if the vault is in jail, false otherwise function setVaultJailStatus(address _vault, bool _isInJail) external onlyRole(REGISTRY_ROLE) { if (_vault == address(0)) revert ZeroArgument("_vault"); ERC7201Storage storage $ = _getStorage(); if ($.isVaultInJail[_vault] == _isInJail) revert VaultInJailAlreadySet(); $.isVaultInJail[_vault] = _isInJail; emit VaultJailStatusUpdated(_vault, _isInJail); } /// @notice Get vault's tier limits /// @param _vault address of the vault /// @return nodeOperator node operator of the vault /// @return tierId tier id of the vault /// @return shareLimit share limit of the vault /// @return reserveRatioBP reserve ratio of the vault /// @return forcedRebalanceThresholdBP forced rebalance threshold of the vault /// @return infraFeeBP infra fee of the vault /// @return liquidityFeeBP liquidity fee of the vault /// @return reservationFeeBP reservation fee of the vault function vaultTierInfo(address _vault) external view returns ( address nodeOperator, uint256 tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ) { ERC7201Storage storage $ = _getStorage(); tierId = $.vaultTier[_vault]; Tier memory t = $.tiers[tierId]; nodeOperator = t.operator; shareLimit = t.shareLimit; reserveRatioBP = t.reserveRatioBP; forcedRebalanceThresholdBP = t.forcedRebalanceThresholdBP; infraFeeBP = t.infraFeeBP; liquidityFeeBP = t.liquidityFeeBP; reservationFeeBP = t.reservationFeeBP; } /// @notice Returns the effective share limit of a vault according to the OperatorGrid and vault share limits /// @param _vault address of the vault /// @return shareLimit effective share limit of the vault function effectiveShareLimit(address _vault) public view returns (uint256) { VaultHub vaultHub = _vaultHub(); uint256 shareLimit = vaultHub.vaultConnection(_vault).shareLimit; uint256 liabilityShares = vaultHub.liabilityShares(_vault); uint256 gridShareLimit = _gridRemainingShareLimit(_vault) + liabilityShares; return Math256.min(gridShareLimit, shareLimit); } /// @notice Returns true if the vault is in jail /// @param _vault address of the vault /// @return true if the vault is in jail function isVaultInJail(address _vault) external view returns (bool) { return _getStorage().isVaultInJail[_vault]; } /// @notice Returns the remaining share limit in a given tier and group /// @param _vault address of the vault /// @return remaining share limit /// @dev remaining share limit inherits the limits of the vault tier and group, /// and accounts liabilities of other vaults belonging to the same tier and group function _gridRemainingShareLimit(address _vault) internal view returns (uint256) { ERC7201Storage storage $ = _getStorage(); uint256 tierId = $.vaultTier[_vault]; Tier storage t = $.tiers[tierId]; uint256 tierLimit = t.shareLimit; uint256 tierRemaining = tierLimit > t.liabilityShares ? tierLimit - t.liabilityShares : 0; if (tierId == DEFAULT_TIER_ID) return tierRemaining; Group storage g = $.groups[t.operator]; uint256 groupLimit = g.shareLimit; uint256 groupRemaining = groupLimit > g.liabilityShares ? groupLimit - g.liabilityShares : 0; return Math256.min(tierRemaining, groupRemaining); } /// @notice Validates tier parameters /// @param _reserveRatioBP Reserve ratio /// @param _forcedRebalanceThresholdBP Forced rebalance threshold /// @param _infraFeeBP Infra fee /// @param _liquidityFeeBP Liquidity fee /// @param _reservationFeeBP Reservation fee function _validateParams( uint256 _tierId, uint256 _reserveRatioBP, uint256 _forcedRebalanceThresholdBP, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) internal pure { if (_reserveRatioBP == 0) revert ZeroArgument("_reserveRatioBP"); if (_reserveRatioBP > MAX_RESERVE_RATIO_BP) revert ReserveRatioTooHigh(_tierId, _reserveRatioBP, MAX_RESERVE_RATIO_BP); if (_forcedRebalanceThresholdBP == 0) revert ZeroArgument("_forcedRebalanceThresholdBP"); if (_forcedRebalanceThresholdBP + 10 >= _reserveRatioBP) { revert ForcedRebalanceThresholdTooHigh(_tierId, _forcedRebalanceThresholdBP, _reserveRatioBP); } if (_infraFeeBP > MAX_FEE_BP) revert InfraFeeTooHigh(_tierId, _infraFeeBP, MAX_FEE_BP); if (_liquidityFeeBP > MAX_FEE_BP) revert LiquidityFeeTooHigh(_tierId, _liquidityFeeBP, MAX_FEE_BP); if (_reservationFeeBP > MAX_FEE_BP) revert ReservationFeeTooHigh(_tierId, _reservationFeeBP, MAX_FEE_BP); } function _vaultHub() internal view returns (VaultHub) { return VaultHub(payable(LIDO_LOCATOR.vaultHub())); } function _getStorage() private pure returns (ERC7201Storage storage $) { assembly { $.slot := OPERATOR_GRID_STORAGE_LOCATION } } function _getVaultContextForConnectedVault(address _vault) internal view returns ( VaultHub vaultHub, VaultHub.VaultConnection memory vaultConnection, address vaultOwner, address nodeOperator, uint256 vaultTierId ) { if (_vault == address(0)) revert ZeroArgument("_vault"); vaultHub = _vaultHub(); if (!vaultHub.isVaultConnected(_vault)) revert VaultNotConnected(); vaultConnection = vaultHub.vaultConnection(_vault); vaultOwner = vaultConnection.owner; nodeOperator = IStakingVault(_vault).nodeOperator(); vaultTierId = _getStorage().vaultTier[_vault]; } function _requireLessOrEqToBP(uint256 _valueBP, uint256 _maxValueBP) internal pure { if (_valueBP > _maxValueBP) revert InvalidBasisPoints(_valueBP, _maxValueBP); } // ----------------------------- // EVENTS // ----------------------------- event GroupAdded(address indexed nodeOperator, uint256 shareLimit); event GroupShareLimitUpdated(address indexed nodeOperator, uint256 shareLimit); event TierAdded( address indexed nodeOperator, uint256 indexed tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event TierChanged(address indexed vault, uint256 indexed tierId, uint256 shareLimit); event TierUpdated( uint256 indexed tierId, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event VaultJailStatusUpdated(address indexed vault, bool isInJail); // ----------------------------- // ERRORS // ----------------------------- error NotAuthorized(string operation, address sender); error ZeroArgument(string argument); error GroupExists(); error GroupNotExists(); error GroupLimitExceeded(); error NodeOperatorNotExists(); error TierLimitExceeded(); error VaultInJailAlreadySet(); error VaultInJail(); error TierNotExists(); error TierAlreadySet(); error TierNotInOperatorGroup(); error CannotChangeToDefaultTier(); error ReserveRatioTooHigh(uint256 tierId, uint256 reserveRatioBP, uint256 maxReserveRatioBP); error ForcedRebalanceThresholdTooHigh(uint256 tierId, uint256 forcedRebalanceThresholdBP, uint256 reserveRatioBP); error InfraFeeTooHigh(uint256 tierId, uint256 infraFeeBP, uint256 maxInfraFeeBP); error LiquidityFeeTooHigh(uint256 tierId, uint256 liquidityFeeBP, uint256 maxLiquidityFeeBP); error ReservationFeeTooHigh(uint256 tierId, uint256 reservationFeeBP, uint256 maxReservationFeeBP); error ArrayLengthMismatch(); error RequestedShareLimitTooHigh(uint256 requestedShareLimit, uint256 tierShareLimit); error RequestedShareLimitTooLow(uint256 requestedSHareLimit, uint256 vaultShares); error VaultNotConnected(); error VaultAlreadySyncedWithTier(); error ShareLimitAlreadySet(); error InvalidBasisPoints(uint256 valueBP, uint256 maxValueBP); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {BeaconProxy} from "@openzeppelin/contracts-v5.2/proxy/beacon/BeaconProxy.sol"; import {PinnedBeaconUtils} from "./lib/PinnedBeaconUtils.sol"; /** * @title PinnedBeaconProxy * @author Lido * @notice * * PinnedBeaconProxy is an extended version of OpenZeppelin's BeaconProxy that adds the ability * to "pin" (ossify) specific implementation versions for individual proxy instances. * * Implementation details: * - Uses PinnedBeaconUtils library to manage pinned implementation state * - Pinned implementation is stored in a storage slot (keccak256("stakingVault.proxy.pinnedBeacon") - 1) * - When ossified, the proxy will always use the pinned implementation instead of the beacon's implementation * */ contract PinnedBeaconProxy is BeaconProxy { constructor(address beacon, bytes memory data) BeaconProxy(beacon, data) payable {} function isOssified() external view returns (bool) { return PinnedBeaconUtils.getPinnedImplementation() != address(0); } function _implementation() internal view virtual override returns (address) { address pinnedImpl = PinnedBeaconUtils.getPinnedImplementation(); if (pinnedImpl != address(0)) { return pinnedImpl; } return super._implementation(); } function implementation() external view returns (address) { return _implementation(); } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {Clones} from "@openzeppelin/contracts-v5.2/proxy/Clones.sol"; import {PinnedBeaconProxy} from "./PinnedBeaconProxy.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {VaultHub} from "./VaultHub.sol"; import {Permissions} from "./dashboard/Permissions.sol"; import {Dashboard} from "./dashboard/Dashboard.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {IVaultFactory} from "./interfaces/IVaultFactory.sol"; /** * @title VaultFactory * @author Lido * @notice The factory contract for StakingVaults */ contract VaultFactory is IVaultFactory { address public immutable LIDO_LOCATOR; address public immutable BEACON; address public immutable DASHBOARD_IMPL; address public immutable PREVIOUS_FACTORY; /** * @notice mapping of vaults deployed by this factory * @dev Only the vaults deployed by this factory can be connected to VaultHub. * This ensures that the vault storage has not been tampered with * before connecting to VaultHub. */ mapping(address vault => bool) private deployedByThisFactory; /** * @param _lidoLocator The address of the LidoLocator contract * @param _beacon The address of the Beacon contract for StakingVaults * @param _dashboardImpl The address of the Dashboard implementation contract * @param _previousFactory the address of the previous factory (can be zero address) */ constructor( address _lidoLocator, address _beacon, address _dashboardImpl, address _previousFactory ) { if (_lidoLocator == address(0)) revert ZeroArgument("_lidoLocator"); if (_beacon == address(0)) revert ZeroArgument("_beacon"); if (_dashboardImpl == address(0)) revert ZeroArgument("_dashboardImpl"); LIDO_LOCATOR = _lidoLocator; BEACON = _beacon; DASHBOARD_IMPL = _dashboardImpl; PREVIOUS_FACTORY = _previousFactory; } /** * Returns true if the vault was deployed by this factory or PREVIOUS_FACTORY * @param _vault address of the vault */ function deployedVaults(address _vault) external view returns (bool) { return deployedByThisFactory[_vault] || (PREVIOUS_FACTORY != address(0) && IVaultFactory(PREVIOUS_FACTORY).deployedVaults(_vault)); } /** * @notice Creates a new StakingVault and Dashboard contracts * @param _defaultAdmin The address of the default admin of the Dashboard * @param _nodeOperator The address of the node operator of the StakingVault * @param _nodeOperatorManager The address of the node operator manager in the Dashboard * @param _nodeOperatorFeeBP The node operator fee in basis points * @param _confirmExpiry The confirmation expiry in seconds * @param _roleAssignments The optional role assignments to be made (only _defaultAdmin sub-roles) */ function createVaultWithDashboard( address _defaultAdmin, address _nodeOperator, address _nodeOperatorManager, uint256 _nodeOperatorFeeBP, uint256 _confirmExpiry, Permissions.RoleAssignment[] calldata _roleAssignments ) external payable returns (IStakingVault vault, Dashboard dashboard) { // check if the msg.value is enough to cover the connect deposit ILidoLocator locator = ILidoLocator(LIDO_LOCATOR); if (msg.value < VaultHub(payable(locator.vaultHub())).CONNECT_DEPOSIT()) revert InsufficientFunds(); // create the vault proxy vault = IStakingVault(_deployVault()); // create the dashboard proxy bytes memory immutableArgs = abi.encode(address(vault)); dashboard = Dashboard(payable(Clones.cloneWithImmutableArgs(DASHBOARD_IMPL, immutableArgs))); // initialize StakingVault with the dashboard address as the owner vault.initialize(address(dashboard), _nodeOperator, locator.predepositGuarantee()); // initialize Dashboard with the factory address as the default admin, grant optional roles and connect to VaultHub dashboard.initialize(address(this), _nodeOperatorManager, _nodeOperatorManager, _nodeOperatorFeeBP, _confirmExpiry); dashboard.connectToVaultHub{value: msg.value}(); if (_roleAssignments.length > 0) dashboard.grantRoles(_roleAssignments); dashboard.grantRole(dashboard.DEFAULT_ADMIN_ROLE(), _defaultAdmin); dashboard.revokeRole(dashboard.DEFAULT_ADMIN_ROLE(), address(this)); emit VaultCreated(address(vault)); emit DashboardCreated(address(dashboard), address(vault), _defaultAdmin); } /** * @notice Creates a new StakingVault and Dashboard contracts without connecting to VaultHub * @param _defaultAdmin The address of the default admin of the Dashboard * @param _nodeOperator The address of the node operator of the StakingVault * @param _nodeOperatorManager The address of the node operator manager in the Dashboard * @param _nodeOperatorFeeBP The node operator fee in basis points * @param _confirmExpiry The confirmation expiry in seconds * @param _roleAssignments The optional role assignments to be made (only _nodeOperatorManager sub-roles) * @notice Only Node Operator managed roles can be assigned */ function createVaultWithDashboardWithoutConnectingToVaultHub( address _defaultAdmin, address _nodeOperator, address _nodeOperatorManager, uint256 _nodeOperatorFeeBP, uint256 _confirmExpiry, Permissions.RoleAssignment[] calldata _roleAssignments ) external returns (IStakingVault vault, Dashboard dashboard) { ILidoLocator locator = ILidoLocator(LIDO_LOCATOR); // create the vault proxy vault = IStakingVault(_deployVault()); // create the dashboard proxy bytes memory immutableArgs = abi.encode(address(vault)); dashboard = Dashboard(payable(Clones.cloneWithImmutableArgs(DASHBOARD_IMPL, immutableArgs))); // initialize StakingVault with the dashboard address as the owner vault.initialize(address(dashboard), _nodeOperator, locator.predepositGuarantee()); // initialize Dashboard with the _defaultAdmin as the default admin, grant optional node operator managed roles dashboard.initialize(_defaultAdmin, address(this), _nodeOperatorManager, _nodeOperatorFeeBP, _confirmExpiry); if (_roleAssignments.length > 0) dashboard.grantRoles(_roleAssignments); dashboard.grantRole(dashboard.NODE_OPERATOR_MANAGER_ROLE(), _nodeOperatorManager); dashboard.revokeRole(dashboard.NODE_OPERATOR_MANAGER_ROLE(), address(this)); emit VaultCreated(address(vault)); emit DashboardCreated(address(dashboard), address(vault), _defaultAdmin); } function _deployVault() internal returns (address vault) { vault = address(new PinnedBeaconProxy(BEACON, "")); deployedByThisFactory[vault] = true; } /** * @notice Event emitted on a Vault creation * @param vault The address of the created Vault */ event VaultCreated(address indexed vault); /** * @notice Event emitted on a Dashboard creation * @param dashboard The address of the created Dashboard * @param vault The address of the created Vault * @param admin The address of the Dashboard admin */ event DashboardCreated(address indexed dashboard, address indexed vault, address indexed admin); /** * @notice Error thrown for when a given value cannot be zero * @param argument Name of the argument */ error ZeroArgument(string argument); /** * @notice Error thrown for when insufficient funds are provided */ error InsufficientFunds(); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {SafeCast} from "@openzeppelin/contracts-v5.2/utils/math/SafeCast.sol"; import {Math256} from "contracts/common/lib/Math256.sol"; import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol"; import {ILido} from "contracts/common/interfaces/ILido.sol"; import {IHashConsensus} from "contracts/common/interfaces/IHashConsensus.sol"; import {IStakingVault} from "./interfaces/IStakingVault.sol"; import {IPredepositGuarantee} from "./interfaces/IPredepositGuarantee.sol"; import {IPinnedBeaconProxy} from "./interfaces/IPinnedBeaconProxy.sol"; import {IVaultFactory} from "./interfaces/IVaultFactory.sol"; import {OperatorGrid} from "./OperatorGrid.sol"; import {LazyOracle} from "./LazyOracle.sol"; import {PausableUntilWithRoles} from "../utils/PausableUntilWithRoles.sol"; import {RefSlotCache, DoubleRefSlotCache, DOUBLE_CACHE_LENGTH} from "./lib/RefSlotCache.sol"; /// @notice VaultHub is a contract that manages StakingVaults connected to the Lido protocol /// It allows to connect and disconnect vaults, mint and burn stETH using vaults as collateral /// Also, it facilitates the individual per-vault reports from the lazy oracle to the vaults and charges Lido fees /// @author folkyatina contract VaultHub is PausableUntilWithRoles { using RefSlotCache for RefSlotCache.Uint104WithCache; using DoubleRefSlotCache for DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH]; // ----------------------------- // STORAGE STRUCTS // ----------------------------- /// @custom:storage-location erc7201:Lido.Vaults.VaultHub struct Storage { /// @notice accounting records for each vault mapping(address vault => VaultRecord) records; /// @notice connection parameters for each vault mapping(address vault => VaultConnection) connections; /// @notice 1-based array of vaults connected to the hub. index 0 is reserved for not connected vaults address[] vaults; /// @notice amount of bad debt that was internalized from the vault to become the protocol loss RefSlotCache.Uint104WithCache badDebtToInternalize; } struct VaultConnection { // ### 1st slot /// @notice address of the vault owner address owner; /// @notice maximum number of stETH shares that can be minted by vault owner uint96 shareLimit; // ### 2nd slot /// @notice index of the vault in the list of vaults. Indexes are not guaranteed to be stable. /// @dev vaultIndex is always greater than 0 uint96 vaultIndex; /// @notice timestamp of the block when disconnection was initiated /// equal 0 if vault is disconnected and max(uint48) - for connected , uint48 disconnectInitiatedTs; /// @notice share of ether that is locked on the vault as an additional reserve /// e.g RR=30% means that for 1stETH minted 1/(1-0.3)=1.428571428571428571 ETH is locked on the vault uint16 reserveRatioBP; /// @notice if vault's reserve decreases to this threshold, it should be force rebalanced uint16 forcedRebalanceThresholdBP; /// @notice infra fee in basis points uint16 infraFeeBP; /// @notice liquidity fee in basis points uint16 liquidityFeeBP; /// @notice reservation fee in basis points uint16 reservationFeeBP; /// @notice if true, vault owner intends to pause the beacon chain deposits bool beaconChainDepositsPauseIntent; /// 24 bits gap } struct VaultRecord { // ### 1st slot /// @notice latest report for the vault Report report; // ### 2nd slot /// @notice max number of shares that was minted by the vault in current Oracle period /// (used to calculate the locked value on the vault) uint96 maxLiabilityShares; /// @notice liability shares of the vault uint96 liabilityShares; // ### 3rd and 4th slots /// @notice inOutDelta of the vault (all deposits - all withdrawals) DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH] inOutDelta; // ### 5th slot /// @notice the minimal value that the reserve part of the locked can be uint128 minimalReserve; /// @notice part of liability shares reserved to be burnt as Lido core redemptions uint128 redemptionShares; // ### 6th slot /// @notice cumulative value for Lido fees that accrued on the vault uint128 cumulativeLidoFees; /// @notice cumulative value for Lido fees that were settled on the vault uint128 settledLidoFees; } struct Report { /// @notice total value of the vault uint104 totalValue; /// @notice inOutDelta of the report int104 inOutDelta; /// @notice timestamp (in seconds) uint48 timestamp; } // ----------------------------- // CONSTANTS // ----------------------------- // some constants are immutables to save bytecode // keccak256(abi.encode(uint256(keccak256("Lido.Vaults.VaultHub")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant STORAGE_LOCATION = 0x9eb73ffa4c77d08d5d1746cf5a5e50a47018b610ea5d728ea9bd9e399b76e200; /// @notice role that allows to disconnect vaults from the hub /// @dev 0x479bc4a51d27fbdc8e51b5b1ebd3dcd58bd229090980bff226f8930587e69ce3 bytes32 public immutable VAULT_MASTER_ROLE = keccak256("vaults.VaultHub.VaultMasterRole"); /// @notice role that allows to accrue Lido Core redemptions on the vault /// @dev 0x44f007e8cc2a08047a03d8d9c295057454942eb49ee3ced9c87e9b9406f21174 bytes32 public immutable REDEMPTION_MASTER_ROLE = keccak256("vaults.VaultHub.RedemptionMasterRole"); /// @notice role that allows to trigger validator exits under extreme conditions /// @dev 0x2159c5943234d9f3a7225b9a743ea06e4a0d0ba5ed82889e867759a8a9eb7883 bytes32 public immutable VALIDATOR_EXIT_ROLE = keccak256("vaults.VaultHub.ValidatorExitRole"); /// @notice role that allows to bail out vaults with bad debt /// @dev 0xa85bab4b576ca359fa6ae02ab8744b5c85c7e7ed4d7e0bca7b5b64580ac5d17d bytes32 public immutable BAD_DEBT_MASTER_ROLE = keccak256("vaults.VaultHub.BadDebtMasterRole"); /// @notice amount of ETH that is locked on the vault on connect and can be withdrawn on disconnect only uint256 public constant CONNECT_DEPOSIT = 1 ether; /// @notice The time delta for report freshness check uint256 public constant REPORT_FRESHNESS_DELTA = 2 days; /// @dev basis points base uint256 internal constant TOTAL_BASIS_POINTS = 100_00; /// @dev special value for `disconnectTimestamp` storage means the vault is not marked for disconnect uint48 internal constant DISCONNECT_NOT_INITIATED = type(uint48).max; /// @notice minimum amount of ether that is required for the beacon chain deposit /// @dev used as a threshold for the beacon chain deposits pause uint256 internal constant MIN_BEACON_DEPOSIT = 1 ether; /// @dev amount of ether required to activate a validator after PDG uint256 internal constant PDG_ACTIVATION_DEPOSIT = 31 ether; // ----------------------------- // IMMUTABLES // ----------------------------- /// @notice limit for a single vault share limit relative to Lido TVL in basis points uint256 public immutable MAX_RELATIVE_SHARE_LIMIT_BP; ILido public immutable LIDO; ILidoLocator public immutable LIDO_LOCATOR; /// @dev it's cached as immutable to save the gas, but it's add some rigidity to the contract structure /// and will require update of the VaultHub if HashConsensus changes IHashConsensus public immutable CONSENSUS_CONTRACT; /// @param _locator Lido Locator contract /// @param _lido Lido stETH contract /// @param _consensusContract Hash consensus contract /// @param _maxRelativeShareLimitBP Maximum share limit relative to TVL in basis points constructor(ILidoLocator _locator, ILido _lido, IHashConsensus _consensusContract, uint256 _maxRelativeShareLimitBP) { _requireNotZero(address(_locator)); _requireNotZero(address(_lido)); _requireNotZero(address(_consensusContract)); _requireNotZero(_maxRelativeShareLimitBP); if (_maxRelativeShareLimitBP > TOTAL_BASIS_POINTS) revert InvalidBasisPoints(_maxRelativeShareLimitBP, TOTAL_BASIS_POINTS); MAX_RELATIVE_SHARE_LIMIT_BP = _maxRelativeShareLimitBP; LIDO_LOCATOR = _locator; LIDO = _lido; CONSENSUS_CONTRACT = _consensusContract; _disableInitializers(); _pauseUntil(PAUSE_INFINITELY); } /// @dev used to perform rebalance operations receive() external payable {} /// @notice initialize the vault hub /// @param _admin default admin address function initialize(address _admin) external initializer { _requireNotZero(_admin); __AccessControlEnumerable_init(); // the stone in the elevator. index 0 is reserved for not connected vaults _storage().vaults.push(address(0)); _grantRole(DEFAULT_ADMIN_ROLE, _admin); } /// @notice returns the number of vaults connected to the hub /// @dev since index 0 is reserved for not connected vaults, it's always 1 less than the vaults array length function vaultsCount() external view returns (uint256) { return _storage().vaults.length - 1; } /// @notice returns the vault address by its index /// @param _index index of the vault in the 1-based list of vaults. possible range [1, vaultsCount()] /// @dev Indexes are guaranteed to be stable only in one transaction. function vaultByIndex(uint256 _index) external view returns (address) { _requireNotZero(_index); return _storage().vaults[_index]; } /// @return connection parameters struct for the given vault /// @dev it returns empty struct if the vault is not connected to the hub /// @dev it may return connection even if it's pending to be disconnected function vaultConnection(address _vault) external view returns (VaultConnection memory) { return _vaultConnection(_vault); } /// @return the accounting record struct for the given vault /// @dev it returns empty struct if the vault is not connected to the hub function vaultRecord(address _vault) external view returns (VaultRecord memory) { return _vaultRecord(_vault); } /// @return true if the vault is connected to the hub or pending to be disconnected function isVaultConnected(address _vault) external view returns (bool) { return _vaultConnection(_vault).vaultIndex != 0; } /// @return true if vault is pending for disconnect, false if vault is connected or disconnected /// @dev disconnect can be performed by applying the report for the period when it was initiated function isPendingDisconnect(address _vault) external view returns (bool) { return _isPendingDisconnect(_vaultConnection(_vault)); } /// @return total value of the vault /// @dev returns 0 if the vault is not connected function totalValue(address _vault) external view returns (uint256) { return _totalValue(_vaultRecord(_vault)); } /// @return liability shares of the vault /// @dev returns 0 if the vault is not connected function liabilityShares(address _vault) external view returns (uint256) { return _vaultRecord(_vault).liabilityShares; } /// @return locked amount of ether for the vault /// @dev returns 0 if the vault is not connected function locked(address _vault) external view returns (uint256) { return _locked(_vaultConnection(_vault), _vaultRecord(_vault)); } /// @return the amount of ether that can be locked in the vault given the current total value /// @dev returns 0 if the vault is not connected function maxLockableValue(address _vault) external view returns (uint256) { return _maxLockableValue(_vaultRecord(_vault), 0); } /// @notice Calculates the total number of shares that is possible to mint on the vault /// @param _vault The address of the vault /// @param _deltaValue The delta value to apply to the total value of the vault (may be negative) /// @return the number of shares that can be minted /// @dev returns 0 if the vault is not connected function totalMintingCapacityShares(address _vault, int256 _deltaValue) external view returns (uint256) { return _totalMintingCapacityShares(_vault, _deltaValue); } /// @return the amount of ether that can be instantly withdrawn from the staking vault /// @dev returns 0 if the vault is not connected or disconnect pending function withdrawableValue(address _vault) external view returns (uint256) { VaultConnection storage connection = _vaultConnection(_vault); if (_isPendingDisconnect(connection)) return 0; return _withdrawableValue(_vault, connection, _vaultRecord(_vault)); } /// @return latest report for the vault /// @dev returns empty struct if the vault is not connected function latestReport(address _vault) external view returns (Report memory) { return _vaultRecord(_vault).report; } /// @return true if the report for the vault is fresh, false otherwise /// @dev returns false if the vault is not connected function isReportFresh(address _vault) external view returns (bool) { return _isReportFresh(_vaultRecord(_vault)); } /// @notice checks if the vault is healthy by comparing its total value after applying forced rebalance threshold /// against current liability shares /// @param _vault vault address /// @return true if vault is healthy, false otherwise /// @dev returns true if the vault is not connected function isVaultHealthy(address _vault) external view returns (bool) { return _isVaultHealthy(_vaultConnection(_vault), _vaultRecord(_vault)); } /// @notice calculate shares amount to make the vault healthy using rebalance /// @param _vault vault address /// @return shares amount or UINT256_MAX if it's impossible to make the vault healthy using rebalance /// @dev returns 0 if the vault is not connected function healthShortfallShares(address _vault) external view returns (uint256) { return _healthShortfallShares(_vaultConnection(_vault), _vaultRecord(_vault)); } /// @notice calculate ether amount required to cover obligations shortfall of the vault /// @param _vault vault address /// @return ether amount or UINT256_MAX if it's impossible to cover obligations shortfall /// @dev returns 0 if the vault is not connected function obligationsShortfallValue(address _vault) external view returns (uint256) { VaultConnection storage connection = _vaultConnection(_vault); if (connection.vaultIndex == 0) return 0; return _obligationsShortfallValue(_vault, connection, _vaultRecord(_vault)); } /// @notice returns the vault's current obligations toward the protocol /// /// Obligations are amounts the vault must cover, in the following priority: /// 1) Maintain healthiness - burn/rebalance liability shares until the health ratio is restored /// 2) Cover redemptions - burn/rebalance part of the liability shares marked as `redemptionShares` /// 3) Pay Lido fees - settle accrued but unsettled fees /// /// Effects: /// - Withdrawals from the vault are limited by the amount required to cover the obligations /// - Beacon chain deposits are auto-paused while the vault is unhealthy, has redemptions to cover, or has /// unsettled fees ≥ `MIN_BEACON_DEPOSIT` (1 ETH) /// /// How to settle: /// - Anyone can: /// - Rebalance shares permissionlessly when there are funds via `forceRebalance` (restores health / covers redemptions) /// - Settle fees permissionlessly when there are funds via `settleLidoFees` /// - The owner (or a trusted role) can trigger validator exits / withdrawals to source ETH when needed /// /// @param _vault vault address /// @return sharesToBurn amount of shares to burn / rebalance /// @return feesToSettle amount of Lido fees to settle /// @dev if the vault has bad debt (i.e. not fixable by rebalance), returns `type(uint256).max` for `sharesToBurn` /// @dev returns (0, 0) if the vault is not connected function obligations(address _vault) external view returns (uint256 sharesToBurn, uint256 feesToSettle) { VaultConnection storage connection = _vaultConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); return ( _obligationsShares(connection, record), _unsettledLidoFeesValue(record) ); } /// @return the amount of Lido fees that currently can be settled. Even if vault's balance is sufficient to cover /// the fees, some amount may be blocked for redemptions, or locked ether /// @dev returns 0 if the vault is not connected function settleableLidoFeesValue(address _vault) external view returns (uint256) { VaultRecord storage record = _vaultRecord(_vault); return _settleableLidoFeesValue(_vault, _vaultConnection(_vault), record, _unsettledLidoFeesValue(record)); } /// @notice amount of bad debt to be internalized to become the protocol loss function badDebtToInternalize() external view returns (uint256) { return _storage().badDebtToInternalize.value; } /// @notice amount of bad debt to be internalized to become the protocol loss (that was actual on the last refSlot) function badDebtToInternalizeForLastRefSlot() external view returns (uint256) { return _storage().badDebtToInternalize.getValueForLastRefSlot(CONSENSUS_CONTRACT); } /// @notice connects a vault to the hub in permissionless way, get limits from the Operator Grid /// @param _vault vault address /// @dev vault should have transferred ownership to the VaultHub contract function connectVault(address _vault) external whenResumed { _requireNotZero(_vault); if (!IVaultFactory(LIDO_LOCATOR.vaultFactory()).deployedVaults(_vault)) revert VaultNotFactoryDeployed(_vault); IStakingVault vault_ = IStakingVault(_vault); _requireSender(vault_.owner()); if (vault_.pendingOwner() != address(this)) revert VaultHubNotPendingOwner(_vault); if (IPinnedBeaconProxy(address(vault_)).isOssified()) revert VaultOssified(_vault); if (vault_.depositor() != address(_predepositGuarantee())) revert PDGNotDepositor(_vault); // we need vault to match staged balance with pendingActivations if (vault_.stagedBalance() != _predepositGuarantee().pendingActivations(vault_) * PDG_ACTIVATION_DEPOSIT) { revert InsufficientStagedBalance(_vault); } ( , // nodeOperatorInTier , // tierId uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ) = _operatorGrid().vaultTierInfo(_vault); _connectVault(_vault, shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, infraFeeBP, liquidityFeeBP, reservationFeeBP ); IStakingVault(_vault).acceptOwnership(); emit VaultConnected({ vault: _vault, shareLimit: shareLimit, reserveRatioBP: reserveRatioBP, forcedRebalanceThresholdBP: forcedRebalanceThresholdBP, infraFeeBP: infraFeeBP, liquidityFeeBP: liquidityFeeBP, reservationFeeBP: reservationFeeBP }); } /// @notice updates a redemption shares on the vault /// @param _vault The address of the vault /// @param _liabilitySharesTarget maximum amount of liabilityShares that will be preserved, the rest will be /// marked as redemptionShares. If value is greater than liabilityShares, redemptionShares are set to 0 /// @dev NB: Mechanism to be triggered when Lido Core TVL <= stVaults TVL function setLiabilitySharesTarget(address _vault, uint256 _liabilitySharesTarget) external onlyRole(REDEMPTION_MASTER_ROLE) { VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); uint256 liabilityShares_ = record.liabilityShares; uint256 redemptionShares = liabilityShares_ > _liabilitySharesTarget ? liabilityShares_ - _liabilitySharesTarget : 0; record.redemptionShares = uint128(redemptionShares); _updateBeaconChainDepositsPause(_vault, record, connection); emit VaultRedemptionSharesUpdated(_vault, record.redemptionShares); } /// @notice updates the vault's connection parameters /// @param _vault vault address /// @param _shareLimit new share limit /// @param _reserveRatioBP new reserve ratio /// @param _forcedRebalanceThresholdBP new forced rebalance threshold /// @param _infraFeeBP new infra fee /// @param _liquidityFeeBP new liquidity fee /// @param _reservationFeeBP new reservation fee /// @dev reverts if the vault's minting capacity will be exceeded with new reserve parameters /// @dev requires the fresh report function updateConnection( address _vault, uint256 _shareLimit, uint256 _reserveRatioBP, uint256 _forcedRebalanceThresholdBP, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) external { _requireSender(address(_operatorGrid())); _requireSaneShareLimit(_shareLimit); VaultConnection storage connection = _vaultConnection(_vault); _requireConnected(connection, _vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); if ( _reserveRatioBP != connection.reserveRatioBP || _forcedRebalanceThresholdBP != connection.forcedRebalanceThresholdBP ) { uint256 totalValue_ = _totalValue(record); uint256 liabilityShares_ = record.liabilityShares; if (_isThresholdBreached(totalValue_, liabilityShares_, _reserveRatioBP)) { revert VaultMintingCapacityExceeded(_vault, totalValue_, liabilityShares_, _reserveRatioBP); } } // special event for the Oracle to track fee calculation emit VaultFeesUpdated({ vault: _vault, preInfraFeeBP: connection.infraFeeBP, preLiquidityFeeBP: connection.liquidityFeeBP, preReservationFeeBP: connection.reservationFeeBP, infraFeeBP: _infraFeeBP, liquidityFeeBP: _liquidityFeeBP, reservationFeeBP: _reservationFeeBP }); connection.shareLimit = uint96(_shareLimit); connection.reserveRatioBP = uint16(_reserveRatioBP); connection.forcedRebalanceThresholdBP = uint16(_forcedRebalanceThresholdBP); connection.infraFeeBP = uint16(_infraFeeBP); connection.liquidityFeeBP = uint16(_liquidityFeeBP); connection.reservationFeeBP = uint16(_reservationFeeBP); emit VaultConnectionUpdated({ vault: _vault, nodeOperator: _nodeOperator(_vault), shareLimit: _shareLimit, reserveRatioBP: _reserveRatioBP, forcedRebalanceThresholdBP: _forcedRebalanceThresholdBP }); } /// @notice disconnect a vault from the hub /// @param _vault vault address /// @dev msg.sender must have VAULT_MASTER_ROLE /// @dev vault's `liabilityShares` should be zero /// @dev requires the fresh report (see _initiateDisconnection) function disconnect(address _vault) external onlyRole(VAULT_MASTER_ROLE) { _initiateDisconnection(_vault, _checkConnection(_vault), _vaultRecord(_vault), false); emit VaultDisconnectInitiated(_vault); } /// @notice update of the vault data by the lazy oracle report /// @param _vault the address of the vault /// @param _reportTimestamp the timestamp of the report (last 32 bits of it) /// @param _reportTotalValue the total value of the vault /// @param _reportInOutDelta the inOutDelta of the vault /// @param _reportCumulativeLidoFees the cumulative Lido fees of the vault /// @param _reportLiabilityShares the liabilityShares of the vault on refSlot /// @param _reportMaxLiabilityShares the maxLiabilityShares of the vault on refSlot /// @param _reportSlashingReserve the slashingReserve of the vault /// @dev NB: LazyOracle sanity checks already verify that the fee can only increase function applyVaultReport( address _vault, uint256 _reportTimestamp, uint256 _reportTotalValue, int256 _reportInOutDelta, uint256 _reportCumulativeLidoFees, uint256 _reportLiabilityShares, uint256 _reportMaxLiabilityShares, uint256 _reportSlashingReserve ) external whenResumed { _requireSender(address(_lazyOracle())); VaultConnection storage connection = _vaultConnection(_vault); _requireConnected(connection, _vault); VaultRecord storage record = _vaultRecord(_vault); if (connection.disconnectInitiatedTs <= _reportTimestamp) { if (_reportSlashingReserve == 0 && record.liabilityShares == 0) { // liabilityShares can increase if badDebt was socialized to this vault IStakingVault(_vault).transferOwnership(connection.owner); _deleteVault(_vault, connection); emit VaultDisconnectCompleted(_vault); return; } else { // we abort the disconnect process as there is a slashing conflict yet to be resolved connection.disconnectInitiatedTs = DISCONNECT_NOT_INITIATED; emit VaultDisconnectAborted(_vault, _reportSlashingReserve); } } _applyVaultReport( record, _reportTimestamp, _reportTotalValue, _reportInOutDelta, _reportCumulativeLidoFees, _reportLiabilityShares, _reportMaxLiabilityShares, _reportSlashingReserve ); emit VaultReportApplied({ vault: _vault, reportTimestamp: _reportTimestamp, reportTotalValue: _reportTotalValue, reportInOutDelta: _reportInOutDelta, reportCumulativeLidoFees: _reportCumulativeLidoFees, reportLiabilityShares: _reportLiabilityShares, reportMaxLiabilityShares: _reportMaxLiabilityShares, reportSlashingReserve: _reportSlashingReserve }); _updateBeaconChainDepositsPause(_vault, record, connection); } /// @notice Transfer the bad debt from the donor vault to the acceptor vault /// @param _badDebtVault address of the vault that has the bad debt /// @param _vaultAcceptor address of the vault that will accept the bad debt /// @param _maxSharesToSocialize maximum amount of shares to socialize /// @return number of shares that was socialized /// (it's limited by acceptor vault capacity and bad debt actual size) /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE /// @dev requires the fresh report for both bad debt and acceptor vaults function socializeBadDebt( address _badDebtVault, address _vaultAcceptor, uint256 _maxSharesToSocialize ) external onlyRole(BAD_DEBT_MASTER_ROLE) returns (uint256) { _requireNotZero(_badDebtVault); _requireNotZero(_vaultAcceptor); _requireNotZero(_maxSharesToSocialize); if (_nodeOperator(_vaultAcceptor) != _nodeOperator(_badDebtVault)) { revert BadDebtSocializationNotAllowed(); } VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault); VaultRecord storage badDebtRecord = _vaultRecord(_badDebtVault); VaultConnection storage acceptorConnection = _vaultConnection(_vaultAcceptor); VaultRecord storage acceptorRecord = _vaultRecord(_vaultAcceptor); _requireConnected(badDebtConnection, _badDebtVault); _requireConnected(acceptorConnection, _vaultAcceptor); _requireFreshReport(_badDebtVault, badDebtRecord); _requireFreshReport(_vaultAcceptor, acceptorRecord); uint256 badDebtShares = _badDebtShares(badDebtRecord); uint256 badDebtToSocialize = Math256.min(badDebtShares, _maxSharesToSocialize); uint256 acceptorTotalValueShares = _getSharesByPooledEth(_totalValue(acceptorRecord)); uint256 acceptorLiabilityShares = acceptorRecord.liabilityShares; // it's possible to socialize up to bad debt: uint256 acceptorCapacity = acceptorTotalValueShares < acceptorLiabilityShares ? 0 : acceptorTotalValueShares - acceptorLiabilityShares; uint256 badDebtSharesToAccept = Math256.min(badDebtToSocialize, acceptorCapacity); if (badDebtSharesToAccept > 0) { _decreaseLiability(_badDebtVault, badDebtRecord, badDebtSharesToAccept); _increaseLiability({ _vault: _vaultAcceptor, _record: acceptorRecord, _amountOfShares: badDebtSharesToAccept, _reserveRatioBP: acceptorConnection.reserveRatioBP, // don't check any limits _lockableValueLimit: type(uint256).max, _shareLimit: type(uint256).max, _overrideOperatorLimits: true }); _updateBeaconChainDepositsPause(_vaultAcceptor, acceptorRecord, acceptorConnection); emit BadDebtSocialized(_badDebtVault, _vaultAcceptor, badDebtSharesToAccept); } return badDebtSharesToAccept; } /// @notice Internalize the bad debt to the protocol /// @param _badDebtVault address of the vault that has the bad debt /// @param _maxSharesToInternalize maximum amount of shares to internalize /// @return number of shares that was internalized (limited by actual size of the bad debt) /// @dev msg.sender must have BAD_DEBT_MASTER_ROLE /// @dev requires the fresh report function internalizeBadDebt( address _badDebtVault, uint256 _maxSharesToInternalize ) external onlyRole(BAD_DEBT_MASTER_ROLE) returns (uint256) { _requireNotZero(_badDebtVault); _requireNotZero(_maxSharesToInternalize); VaultConnection storage badDebtConnection = _vaultConnection(_badDebtVault); VaultRecord storage badDebtRecord = _vaultRecord(_badDebtVault); _requireConnected(badDebtConnection, _badDebtVault); _requireFreshReport(_badDebtVault, badDebtRecord); uint256 badDebtShares = _badDebtShares(badDebtRecord); uint256 badDebtToInternalize_ = Math256.min(badDebtShares, _maxSharesToInternalize); if (badDebtToInternalize_ > 0) { _decreaseLiability(_badDebtVault, badDebtRecord, badDebtToInternalize_); // store internalization in a separate counter that will be settled // by the Accounting Oracle during the report _storage().badDebtToInternalize = _storage().badDebtToInternalize.withValueIncrease({ _consensus: CONSENSUS_CONTRACT, _increment: SafeCast.toUint104(badDebtToInternalize_) }); emit BadDebtWrittenOffToBeInternalized(_badDebtVault, badDebtToInternalize_); } return badDebtToInternalize_; } /// @notice Reset the internalized bad debt to zero /// @dev msg.sender must be the accounting contract function decreaseInternalizedBadDebt(uint256 _amountOfShares) external { _requireSender(LIDO_LOCATOR.accounting()); // don't cache previous value, we don't need it for sure _storage().badDebtToInternalize.value -= uint104(_amountOfShares); } /// @notice transfer the ownership of the vault to a new owner without disconnecting it from the hub /// @param _vault vault address /// @param _newOwner new owner address /// @dev msg.sender should be vault's owner function transferVaultOwnership(address _vault, address _newOwner) external { _requireNotZero(_newOwner); VaultConnection storage connection = _checkConnection(_vault); address oldOwner = connection.owner; _requireSender(oldOwner); connection.owner = _newOwner; emit VaultOwnershipTransferred({ vault: _vault, newOwner: _newOwner, oldOwner: oldOwner }); } /// @notice disconnects a vault from the hub /// @param _vault vault address /// @dev msg.sender should be vault's owner /// @dev vault's `liabilityShares` should be zero /// @dev requires the fresh report (see _initiateDisconnection) function voluntaryDisconnect(address _vault) external whenResumed { VaultConnection storage connection = _checkConnectionAndOwner(_vault); _initiateDisconnection(_vault, connection, _vaultRecord(_vault), true); emit VaultDisconnectInitiated(_vault); } /// @notice funds the vault passing ether as msg.value /// @param _vault vault address /// @dev msg.sender should be vault's owner function fund(address _vault) external payable whenResumed { _requireNotZero(_vault); VaultConnection storage connection = _vaultConnection(_vault); _requireConnected(connection, _vault); _requireSender(connection.owner); _updateInOutDelta(_vault, _vaultRecord(_vault), int104(int256(msg.value))); IStakingVault(_vault).fund{value: msg.value}(); } /// @notice withdraws ether from the vault to the recipient address /// @param _vault vault address /// @param _recipient recipient address /// @param _ether amount of ether to withdraw /// @dev msg.sender should be vault's owner /// @dev requires the fresh report function withdraw(address _vault, address _recipient, uint256 _ether) external whenResumed { VaultConnection storage connection = _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); uint256 withdrawable = _withdrawableValue(_vault, connection, record); if (_ether > withdrawable) { revert AmountExceedsWithdrawableValue(_vault, withdrawable, _ether); } _withdraw(_vault, record, _recipient, _ether); } /// @notice Rebalances StakingVault by withdrawing ether to VaultHub /// @param _vault vault address /// @param _shares amount of shares to rebalance /// @dev msg.sender should be vault's owner /// @dev requires the fresh report function rebalance(address _vault, uint256 _shares) external whenResumed { _requireNotZero(_shares); _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); _rebalance(_vault, record, _shares); } /// @notice mint StETH shares backed by vault external balance to the receiver address /// @param _vault vault address /// @param _recipient address of the receiver /// @param _amountOfShares amount of stETH shares to mint /// @dev requires the fresh report function mintShares(address _vault, address _recipient, uint256 _amountOfShares) external whenResumed { _requireNotZero(_recipient); _requireNotZero(_amountOfShares); VaultConnection storage connection = _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); _increaseLiability({ _vault: _vault, _record: record, _amountOfShares: _amountOfShares, _reserveRatioBP: connection.reserveRatioBP, _lockableValueLimit: _maxLockableValue(record, 0), _shareLimit: connection.shareLimit, _overrideOperatorLimits: false }); LIDO.mintExternalShares(_recipient, _amountOfShares); emit MintedSharesOnVault(_vault, _amountOfShares, _locked(connection, record)); } /// @notice burn steth shares from the balance of the VaultHub contract /// @param _vault vault address /// @param _amountOfShares amount of shares to burn /// @dev msg.sender should be vault's owner /// @dev this function is designed to be used by the smart contract, for EOA see `transferAndBurnShares` function burnShares(address _vault, uint256 _amountOfShares) public whenResumed { _requireNotZero(_amountOfShares); _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); _decreaseLiability(_vault, record, _amountOfShares); LIDO.burnExternalShares(_amountOfShares); _updateBeaconChainDepositsPause(_vault, record, _vaultConnection(_vault)); emit BurnedSharesOnVault(_vault, _amountOfShares); } /// @notice separate burn function for EOA vault owners; requires vaultHub to be approved to transfer stETH /// @param _vault vault address /// @param _amountOfShares amount of shares to transfer and burn /// @dev msg.sender should be vault's owner function transferAndBurnShares(address _vault, uint256 _amountOfShares) external { LIDO.transferSharesFrom(msg.sender, address(this), _amountOfShares); burnShares(_vault, _amountOfShares); } /// @notice pauses beacon chain deposits for the vault /// @param _vault vault address /// @dev msg.sender should be vault's owner function pauseBeaconChainDeposits(address _vault) external { VaultConnection storage connection = _checkConnectionAndOwner(_vault); if (connection.beaconChainDepositsPauseIntent) revert PauseIntentAlreadySet(); connection.beaconChainDepositsPauseIntent = true; emit BeaconChainDepositsPauseIntentSet(_vault, true); _pauseBeaconChainDepositsIfNotAlready(IStakingVault(_vault)); } /// @notice resumes beacon chain deposits for the vault /// @param _vault vault address /// @dev msg.sender should be vault's owner /// @dev requires the fresh report /// @dev NB: if the vault has outstanding obligations, this call will clear the manual pause flag but deposits will /// remain paused until the obligations are covered. Once covered, deposits will resume automatically function resumeBeaconChainDeposits(address _vault) external { VaultConnection storage connection = _checkConnectionAndOwner(_vault); if (!connection.beaconChainDepositsPauseIntent) revert PauseIntentAlreadyUnset(); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); connection.beaconChainDepositsPauseIntent = false; emit BeaconChainDepositsPauseIntentSet(_vault, false); _updateBeaconChainDepositsPause(_vault, record, connection); } /// @notice Emits a request event for the node operator to perform validator exit /// @param _vault vault address /// @param _pubkeys array of public keys of the validators to exit /// @dev msg.sender should be vault's owner function requestValidatorExit(address _vault, bytes calldata _pubkeys) external { _checkConnectionAndOwner(_vault); IStakingVault(_vault).requestValidatorExit(_pubkeys); } /// @notice Triggers validator withdrawals for the vault using EIP-7002 /// @param _vault vault address /// @param _pubkeys array of public keys of the validators to withdraw from /// @param _amountsInGwei array of amounts to withdraw from each validator (0 for full withdrawal) /// @param _refundRecipient address that will receive the refund for transaction costs /// @dev msg.sender should be vault's owner /// @dev requires the fresh report (in case of partial withdrawals) /// @dev A withdrawal fee must be paid via msg.value. /// `StakingVault.calculateValidatorWithdrawalFee()` can be used to calculate the approximate fee amount but /// it's accurate only for the current block. The fee may change when the tx is included, so it's recommended /// to send some surplus. The exact amount required will be paid and the excess will be refunded to the /// `_refundRecipient` address. The fee required can grow exponentially, so limit msg.value wisely to avoid /// overspending. function triggerValidatorWithdrawals( address _vault, bytes calldata _pubkeys, uint64[] calldata _amountsInGwei, address _refundRecipient ) external payable { VaultConnection storage connection = _checkConnectionAndOwner(_vault); VaultRecord storage record = _vaultRecord(_vault); uint256 minPartialAmountInGwei = type(uint256).max; for (uint256 i = 0; i < _amountsInGwei.length; i++) { if (_amountsInGwei[i] > 0 && _amountsInGwei[i] < minPartialAmountInGwei) { minPartialAmountInGwei = _amountsInGwei[i]; } } if (minPartialAmountInGwei < type(uint256).max) { _requireFreshReport(_vault, record); /// @dev NB: Disallow partial withdrawals when the vault has obligations shortfall in order to prevent the /// vault owner from clogging the consensus layer withdrawal queue by front-running and delaying the /// forceful validator exits required for rebalancing the vault. Partial withdrawals only allowed if /// the requested amount of withdrawals is enough to cover the uncovered obligations. uint256 obligationsShortfallAmount = _obligationsShortfallValue(_vault, connection, record); if (obligationsShortfallAmount > 0 && minPartialAmountInGwei * 1e9 < obligationsShortfallAmount) { revert PartialValidatorWithdrawalNotAllowed(); } } _triggerVaultValidatorWithdrawals(_vault, msg.value, _pubkeys, _amountsInGwei, _refundRecipient); } /// @notice Triggers validator full withdrawals for the vault using EIP-7002 if the vault has obligations shortfall /// @param _vault address of the vault to exit validators from /// @param _pubkeys array of public keys of the validators to exit /// @param _refundRecipient address that will receive the refund for transaction costs /// @dev In case the vault has obligations shortfall, trusted actor with the role can force its validators to /// exit the beacon chain. This returns the vault's deposited ETH back to vault's balance and allows to /// rebalance the vault /// @dev requires the fresh report /// @dev A withdrawal fee must be paid via msg.value. /// `StakingVault.calculateValidatorWithdrawalFee()` can be used to calculate the approximate fee amount but /// it's accurate only for the current block. The fee may change when the tx is included, so it's recommended /// to send some surplus. The exact amount required will be paid and the excess will be refunded to the /// `_refundRecipient` address. The fee required can grow exponentially, so limit msg.value wisely to avoid /// overspending. function forceValidatorExit( address _vault, bytes calldata _pubkeys, address _refundRecipient ) external payable onlyRole(VALIDATOR_EXIT_ROLE) { VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); uint256 obligationsShortfallAmount = _obligationsShortfallValue(_vault, connection, record); if (obligationsShortfallAmount == 0) revert ForcedValidatorExitNotAllowed(); uint64[] memory amountsInGwei = new uint64[](0); _triggerVaultValidatorWithdrawals(_vault, msg.value, _pubkeys, amountsInGwei, _refundRecipient); emit ForcedValidatorExitTriggered(_vault, _pubkeys, _refundRecipient); } /// @notice allows anyone to rebalance a vault with an obligations shortfall /// @param _vault vault address /// @dev uses all available ether in the vault to cover outstanding obligations and restore vault health; this /// operation does not settle Lido fees /// @dev requires the fresh report function forceRebalance(address _vault) external { VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); uint256 availableBalance = Math256.min(_availableBalance(_vault), _totalValue(record)); if (availableBalance == 0) revert NoFundsForForceRebalance(_vault); uint256 sharesToForceRebalance = Math256.min( _obligationsShares(connection, record), _getSharesByPooledEth(availableBalance) ); if (sharesToForceRebalance == 0) revert NoReasonForForceRebalance(_vault); _rebalance(_vault, record, sharesToForceRebalance); } /// @notice allows anyone to settle any outstanding Lido fees for a vault, sending them to the treasury /// @param _vault vault address /// @dev requires the fresh report function settleLidoFees(address _vault) external { VaultConnection storage connection = _checkConnection(_vault); VaultRecord storage record = _vaultRecord(_vault); _requireFreshReport(_vault, record); uint256 unsettledLidoFees = _unsettledLidoFeesValue(record); if (unsettledLidoFees == 0) revert NoUnsettledLidoFeesToSettle(_vault); uint256 valueToSettle = _settleableLidoFeesValue(_vault, connection, record, unsettledLidoFees); if (valueToSettle == 0) revert NoFundsToSettleLidoFees(_vault, unsettledLidoFees); _settleLidoFees(_vault, record, connection, valueToSettle); } /// @notice Proves that validators unknown to PDG have correct WC to participate in the vault /// @param _vault vault address /// @param _witness ValidatorWitness struct proving validator WC belonging to staking vault function proveUnknownValidatorToPDG( address _vault, IPredepositGuarantee.ValidatorWitness calldata _witness ) external { _checkConnectionAndOwner(_vault); _predepositGuarantee().proveUnknownValidator(_witness, IStakingVault(_vault)); } /// @notice collects ERC20 tokens from vault /// @param _vault vault address /// @param _token address of the ERC20 token to collect /// @param _recipient address to send collected tokens to /// @param _amount amount of tokens to collect /// @dev will revert with ZeroArgument() if _token, _recipient or _amount is zero /// @dev will revert with EthCollectionNotAllowed() if _token is ETH (via EIP-7528 address) function collectERC20FromVault( address _vault, address _token, address _recipient, uint256 _amount ) external { _checkConnectionAndOwner(_vault); IStakingVault(_vault).collectERC20(_token, _recipient, _amount); } function _connectVault( address _vault, uint256 _shareLimit, uint256 _reserveRatioBP, uint256 _forcedRebalanceThresholdBP, uint256 _infraFeeBP, uint256 _liquidityFeeBP, uint256 _reservationFeeBP ) internal { _requireSaneShareLimit(_shareLimit); VaultConnection memory connection = _vaultConnection(_vault); if (connection.vaultIndex != 0) revert AlreadyConnected(_vault, connection.vaultIndex); uint256 vaultBalance = _availableBalance(_vault); if (vaultBalance < CONNECT_DEPOSIT) revert VaultInsufficientBalance(_vault, vaultBalance, CONNECT_DEPOSIT); IStakingVault vault = IStakingVault(_vault); // Connecting a new vault with totalValue == balance VaultRecord memory record = VaultRecord({ report: Report({ totalValue: uint104(vaultBalance), inOutDelta: int104(int256(vaultBalance)), timestamp: uint48(block.timestamp) }), maxLiabilityShares: 0, liabilityShares: 0, inOutDelta: DoubleRefSlotCache.initializeInt104DoubleCache(int104(int256(vaultBalance))), minimalReserve: uint128(CONNECT_DEPOSIT), redemptionShares: 0, cumulativeLidoFees: 0, settledLidoFees: 0 }); connection = VaultConnection({ owner: vault.owner(), shareLimit: uint96(_shareLimit), vaultIndex: uint96(_storage().vaults.length), disconnectInitiatedTs: DISCONNECT_NOT_INITIATED, reserveRatioBP: uint16(_reserveRatioBP), forcedRebalanceThresholdBP: uint16(_forcedRebalanceThresholdBP), infraFeeBP: uint16(_infraFeeBP), liquidityFeeBP: uint16(_liquidityFeeBP), reservationFeeBP: uint16(_reservationFeeBP), beaconChainDepositsPauseIntent: vault.beaconChainDepositsPaused() }); _addVault(_vault, connection, record); } function _initiateDisconnection( address _vault, VaultConnection storage _connection, VaultRecord storage _record, bool _forceFullFeesSettlement ) internal { _requireFreshReport(_vault, _record); uint256 liabilityShares_ = _record.liabilityShares; if (liabilityShares_ > 0) revert NoLiabilitySharesShouldBeLeft(_vault, liabilityShares_); uint256 unsettledLidoFees = _unsettledLidoFeesValue(_record); if (unsettledLidoFees > 0) { uint256 balance = Math256.min(_availableBalance(_vault), _totalValue(_record)); if (_forceFullFeesSettlement) { if (balance < unsettledLidoFees) revert NoUnsettledLidoFeesShouldBeLeft(_vault, unsettledLidoFees); _settleLidoFees(_vault, _record, _connection, unsettledLidoFees); } else { uint256 withdrawable = Math256.min(balance, unsettledLidoFees); if (withdrawable > 0) { _settleLidoFees(_vault, _record, _connection, withdrawable); } } } _connection.disconnectInitiatedTs = uint48(block.timestamp); } function _applyVaultReport( VaultRecord storage _record, uint256 _reportTimestamp, uint256 _reportTotalValue, int256 _reportInOutDelta, uint256 _reportCumulativeLidoFees, uint256 _reportLiabilityShares, uint256 _reportMaxLiabilityShares, uint256 _reportSlashingReserve ) internal { _record.cumulativeLidoFees = uint128(_reportCumulativeLidoFees); _record.minimalReserve = uint128(Math256.max(CONNECT_DEPOSIT, _reportSlashingReserve)); // We want to prevent 1 tx looping here: // 1. bring ETH (TV+) // 2. mint stETH (locked+) // 3. burn stETH // 4. bring the last report (locked-) // 5. withdraw ETH(TV-) // current maxLiabilityShares will be greater than the report one // if any stETH is minted on funds added after the refslot // in that case we don't update it (preventing unlock) if (_record.maxLiabilityShares == _reportMaxLiabilityShares) { _record.maxLiabilityShares = uint96(Math256.max(_record.liabilityShares, _reportLiabilityShares)); } _record.report = Report({ totalValue: uint104(_reportTotalValue), inOutDelta: int104(_reportInOutDelta), timestamp: uint48(_reportTimestamp) }); } function _rebalance(address _vault, VaultRecord storage _record, uint256 _shares) internal { uint256 valueToRebalance = _getPooledEthBySharesRoundUp(_shares); _decreaseLiability(_vault, _record, _shares); _withdraw(_vault, _record, address(this), valueToRebalance); _rebalanceExternalEtherToInternal(valueToRebalance, _shares); _updateBeaconChainDepositsPause(_vault, _record, _vaultConnection(_vault)); emit VaultRebalanced(_vault, _shares, valueToRebalance); } function _withdraw(address _vault, VaultRecord storage _record, address _recipient, uint256 _amount) internal { uint256 totalValue_ = _totalValue(_record); if (_amount > totalValue_) { revert AmountExceedsTotalValue(_vault, totalValue_, _amount); } _updateInOutDelta(_vault, _record, -int104(int256(_amount))); _withdrawFromVault(_vault, _recipient, _amount); } /// @dev Increases liabilityShares of the vault and updates the locked amount function _increaseLiability( address _vault, VaultRecord storage _record, uint256 _amountOfShares, uint256 _reserveRatioBP, uint256 _lockableValueLimit, uint256 _shareLimit, bool _overrideOperatorLimits ) internal { uint256 sharesAfterMint = _record.liabilityShares + _amountOfShares; if (sharesAfterMint > _shareLimit) { revert ShareLimitExceeded(_vault, sharesAfterMint, _shareLimit); } // Calculate the minimum ETH that needs to be locked in the vault to maintain the reserve ratio uint256 etherToLock = _locked(sharesAfterMint, _record.minimalReserve, _reserveRatioBP); if (etherToLock > _lockableValueLimit) { revert InsufficientValue(_vault, etherToLock, _lockableValueLimit); } if (sharesAfterMint > _record.maxLiabilityShares) { _record.maxLiabilityShares = uint96(sharesAfterMint); } _record.liabilityShares = uint96(sharesAfterMint); _operatorGrid().onMintedShares(_vault, _amountOfShares, _overrideOperatorLimits); } function _decreaseLiability(address _vault, VaultRecord storage _record, uint256 _amountOfShares) internal { uint256 liabilityShares_ = _record.liabilityShares; if (liabilityShares_ < _amountOfShares) revert InsufficientSharesToBurn(_vault, liabilityShares_); _record.liabilityShares = uint96(liabilityShares_ - _amountOfShares); uint256 redemptionShares = _record.redemptionShares; if (_amountOfShares > 0 && redemptionShares > 0) { uint256 decreasedRedemptionShares = redemptionShares - Math256.min(redemptionShares, _amountOfShares); _record.redemptionShares = uint128(decreasedRedemptionShares); emit VaultRedemptionSharesUpdated(_vault, decreasedRedemptionShares); } _operatorGrid().onBurnedShares(_vault, _amountOfShares); } function _badDebtShares(VaultRecord storage _record) internal view returns (uint256) { uint256 liabilityShares_ = _record.liabilityShares; uint256 totalValueShares = _getSharesByPooledEth(_totalValue(_record)); if (totalValueShares > liabilityShares_) { return 0; } return liabilityShares_ - totalValueShares; } function _healthShortfallShares( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 totalValue_ = _totalValue(_record); uint256 liabilityShares_ = _record.liabilityShares; bool isHealthy = !_isThresholdBreached( totalValue_, liabilityShares_, _connection.forcedRebalanceThresholdBP ); // Health vault do not need to rebalance if (isHealthy) { return 0; } uint256 reserveRatioBP = _connection.reserveRatioBP; uint256 maxMintableRatio = (TOTAL_BASIS_POINTS - reserveRatioBP); uint256 liability = _getPooledEthBySharesRoundUp(liabilityShares_); // Impossible to rebalance a vault with bad debt if (liability > totalValue_) { return type(uint256).max; } // if not healthy and low in debt, please rebalance the whole amount if (liabilityShares_ <= 100) return liabilityShares_; // Solve the equation for X: // L - liability, TV - totalValue // MR - maxMintableRatio, 100 - TOTAL_BASIS_POINTS, RR - reserveRatio // X - amount of ether that should be withdrawn (TV - X) and used to repay the debt (L - X) to reduce the // L/TV ratio back to MR // (L - X) / (TV - X) = MR / 100 // (L - X) * 100 = (TV - X) * MR // L * 100 - X * 100 = TV * MR - X * MR // X * MR - X * 100 = TV * MR - L * 100 // X * (MR - 100) = TV * MR - L * 100 // X = (TV * MR - L * 100) / (MR - 100) // X = (L * 100 - TV * MR) / (100 - MR) // RR = 100 - MR // X = (L * 100 - TV * MR) / RR uint256 shortfallEth = Math256.ceilDiv(liability * TOTAL_BASIS_POINTS - totalValue_ * maxMintableRatio, reserveRatioBP); // Add 100 extra shares to avoid dealing with rounding/precision issues uint256 shortfallShares = _getSharesByPooledEth(shortfallEth) + 100; return Math256.min(shortfallShares, liabilityShares_); } function _totalValue(VaultRecord storage _record) internal view returns (uint256) { Report memory report = _record.report; DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH] memory inOutDelta = _record.inOutDelta; return SafeCast.toUint256(int256(uint256(report.totalValue)) + inOutDelta.currentValue() - report.inOutDelta); } function _locked( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { return _locked(_record.maxLiabilityShares, _record.minimalReserve, _connection.reserveRatioBP); } /// @param _liabilityShares amount of shares that the vault is minted /// @param _minimalReserve minimal amount of additional reserve to be locked /// @param _reserveRatioBP the reserve ratio of the vault /// @return the amount of collateral to be locked on the vault function _locked( uint256 _liabilityShares, uint256 _minimalReserve, uint256 _reserveRatioBP ) internal view returns (uint256) { uint256 liability = _getPooledEthBySharesRoundUp(_liabilityShares); // uint256 reserve = liability * TOTAL_BASIS_POINTS / (TOTAL_BASIS_POINTS - _reserveRatioBP) - liability; // simplified to: uint256 reserve = Math256.ceilDiv(liability * _reserveRatioBP, TOTAL_BASIS_POINTS - _reserveRatioBP); return liability + Math256.max(reserve, _minimalReserve); } function _isReportFresh(VaultRecord storage _record) internal view returns (bool) { uint256 latestReportTimestamp = _lazyOracle().latestReportTimestamp(); return // check if AccountingOracle brought fresh report uint48(latestReportTimestamp) <= _record.report.timestamp && // if Accounting Oracle stop bringing the report, last report is fresh during this time block.timestamp - latestReportTimestamp < REPORT_FRESHNESS_DELTA; } function _isVaultHealthy( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (bool) { return !_isThresholdBreached( _totalValue(_record), _record.liabilityShares, _connection.forcedRebalanceThresholdBP ); } /// @dev Returns true if the vault liability breached the given threshold (inverted) function _isThresholdBreached( uint256 _vaultTotalValue, uint256 _vaultLiabilityShares, uint256 _thresholdBP ) internal view returns (bool) { uint256 liability = _getPooledEthBySharesRoundUp(_vaultLiabilityShares); return liability > _vaultTotalValue * (TOTAL_BASIS_POINTS - _thresholdBP) / TOTAL_BASIS_POINTS; } /// @return the total amount of ether needed to fully cover all outstanding obligations of the vault, including: /// - shares to burn required to restore vault healthiness or cover redemptions /// - unsettled Lido fees (if above the minimum beacon deposit) function _obligationsAmount( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 sharesToBurn = _obligationsShares(_connection, _record); if (sharesToBurn == type(uint256).max) return type(uint256).max; // no need to cover fees if they are less than the minimum beacon deposit uint256 unsettledLidoFees = _unsettledLidoFeesValue(_record); uint256 feesToSettle = unsettledLidoFees < MIN_BEACON_DEPOSIT ? 0 : unsettledLidoFees; return _getPooledEthBySharesRoundUp(sharesToBurn) + feesToSettle; } /// @return the ether shortfall required to fully cover all outstanding obligations amount of the vault function _obligationsShortfallValue( address _vault, VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 obligationsAmount_ = _obligationsAmount(_connection, _record); if (obligationsAmount_ == type(uint256).max) return type(uint256).max; uint256 balance = _availableBalance(_vault); return obligationsAmount_ > balance ? obligationsAmount_ - balance : 0; } function _addVault(address _vault, VaultConnection memory _connection, VaultRecord memory _record) internal { Storage storage $ = _storage(); $.vaults.push(_vault); $.connections[_vault] = _connection; $.records[_vault] = _record; } function _deleteVault(address _vault, VaultConnection storage _connection) internal { Storage storage $ = _storage(); uint96 vaultIndex = _connection.vaultIndex; address lastVault = $.vaults[$.vaults.length - 1]; $.connections[lastVault].vaultIndex = vaultIndex; $.vaults[vaultIndex] = lastVault; $.vaults.pop(); delete $.connections[_vault]; delete $.records[_vault]; _lazyOracle().removeVaultQuarantine(_vault); _operatorGrid().resetVaultTier(_vault); } function _checkConnectionAndOwner(address _vault) internal view returns (VaultConnection storage connection) { connection = _checkConnection(_vault); _requireSender(connection.owner); } function _isPendingDisconnect(VaultConnection storage _connection) internal view returns (bool) { uint256 disconnectionTs = _connection.disconnectInitiatedTs; return disconnectionTs != 0 // vault is disconnected && disconnectionTs != DISCONNECT_NOT_INITIATED; // vault in connected but not pending for disconnect } function _checkConnection(address _vault) internal view returns (VaultConnection storage) { _requireNotZero(_vault); VaultConnection storage connection = _vaultConnection(_vault); _requireConnected(connection, _vault); if (_isPendingDisconnect(connection)) revert VaultIsDisconnecting(_vault); return connection; } /// @dev Caches the inOutDelta of the latest refSlot and updates the value function _updateInOutDelta(address _vault, VaultRecord storage _record, int104 _increment) internal { DoubleRefSlotCache.Int104WithCache[DOUBLE_CACHE_LENGTH] memory inOutDelta = _record.inOutDelta.withValueIncrease({ _consensus: CONSENSUS_CONTRACT, _increment: _increment }); _record.inOutDelta = inOutDelta; emit VaultInOutDeltaUpdated(_vault, inOutDelta.currentValue()); } function _updateBeaconChainDepositsPause( address _vault, VaultRecord storage _record, VaultConnection storage _connection ) internal { IStakingVault vault_ = IStakingVault(_vault); uint256 obligationsAmount_ = _obligationsAmount(_connection, _record); if (obligationsAmount_ > 0) { _pauseBeaconChainDepositsIfNotAlready(vault_); } else if (!_connection.beaconChainDepositsPauseIntent) { _resumeBeaconChainDepositsIfNotAlready(vault_); } } function _settleLidoFees( address _vault, VaultRecord storage _record, VaultConnection storage _connection, uint256 _valueToSettle ) internal { uint256 settledLidoFees = _record.settledLidoFees + _valueToSettle; _record.settledLidoFees = uint128(settledLidoFees); _withdraw(_vault, _record, LIDO_LOCATOR.treasury(), _valueToSettle); _updateBeaconChainDepositsPause(_vault, _record, _connection); emit LidoFeesSettled({ vault: _vault, transferred: _valueToSettle, cumulativeLidoFees: _record.cumulativeLidoFees, settledLidoFees: settledLidoFees }); } /// @notice the amount of ether that can be withdrawn from the vault based on the available balance, /// locked value, vault redemption shares (does not include Lido fees) function _withdrawableValueFeesIncluded( address _vault, VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 availableBalance = Math256.min(_availableBalance(_vault), _totalValue(_record)); // We can't withdraw funds that can be used to cover redemptions uint256 redemptionValue = _getPooledEthBySharesRoundUp(_record.redemptionShares); if (redemptionValue > availableBalance) return 0; availableBalance -= redemptionValue; // We must account vaults locked value when calculating the withdrawable amount return Math256.min(availableBalance, _unlocked(_connection, _record)); } /// @notice the amount of lido fees that can be settled on the vault based on the withdrawable value function _settleableLidoFeesValue( address _vault, VaultConnection storage _connection, VaultRecord storage _record, uint256 _feesToSettle ) internal view returns (uint256) { return Math256.min(_withdrawableValueFeesIncluded(_vault, _connection, _record), _feesToSettle); } /// @notice the amount of ether that can be instantly withdrawn from the vault based on the available balance, /// locked value, vault redemption shares and unsettled Lido fees accrued on the vault function _withdrawableValue( address _vault, VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 withdrawable = _withdrawableValueFeesIncluded(_vault, _connection, _record); uint256 feesValue = _unsettledLidoFeesValue(_record); return withdrawable > feesValue ? withdrawable - feesValue : 0; } /// @notice Calculates the max lockable value of the vault /// @param _record The record of the vault /// @param _deltaValue The delta value to apply to the total value of the vault (may be negative) /// @return the max lockable value of the vault function _maxLockableValue(VaultRecord storage _record, int256 _deltaValue) internal view returns (uint256) { uint256 totalValue_ = _totalValue(_record); uint256 unsettledLidoFees_ = _unsettledLidoFeesValue(_record); if (_deltaValue < 0) { uint256 absDeltaValue = uint256(-_deltaValue); totalValue_ = totalValue_ > absDeltaValue ? totalValue_ - absDeltaValue : 0; } else { totalValue_ += uint256(_deltaValue); } return totalValue_ > unsettledLidoFees_ ? totalValue_ - unsettledLidoFees_ : 0; } /// @notice Calculates the total number of shares that is possible to mint on the vault taking into account /// minimal reserve, reserve ratio and the operator grid share limit /// @param _vault The address of the vault /// @param _deltaValue The delta value to apply to the total value of the vault (may be negative) /// @return the number of shares that can be minted /// @dev returns 0 if the vault is not connected function _totalMintingCapacityShares(address _vault, int256 _deltaValue) internal view returns (uint256) { VaultRecord storage record = _vaultRecord(_vault); VaultConnection storage connection = _vaultConnection(_vault); uint256 maxLockableValue_ = _maxLockableValue(record, _deltaValue); uint256 minimalReserve_ = record.minimalReserve; if (maxLockableValue_ <= minimalReserve_) return 0; uint256 reserve = Math256.ceilDiv(maxLockableValue_ * connection.reserveRatioBP, TOTAL_BASIS_POINTS); uint256 capacityShares = _getSharesByPooledEth(maxLockableValue_ - Math256.max(reserve, minimalReserve_)); return Math256.min(capacityShares, _operatorGrid().effectiveShareLimit(_vault)); } function _unlocked( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { uint256 totalValue_ = _totalValue(_record); uint256 locked_ = _locked(_connection, _record); return totalValue_ > locked_ ? totalValue_ - locked_ : 0; } function _unsettledLidoFeesValue(VaultRecord storage _record) internal view returns (uint256) { return _record.cumulativeLidoFees - _record.settledLidoFees; } function _obligationsShares( VaultConnection storage _connection, VaultRecord storage _record ) internal view returns (uint256) { return Math256.max(_healthShortfallShares(_connection, _record), _record.redemptionShares); } function _storage() internal pure returns (Storage storage $) { assembly { $.slot := STORAGE_LOCATION } } function _vaultConnection(address _vault) internal view returns (VaultConnection storage) { return _storage().connections[_vault]; } function _vaultRecord(address _vault) internal view returns (VaultRecord storage) { return _storage().records[_vault]; } // ----------------------------- // EXTERNAL CALLS // ----------------------------- // All external calls that is used more than once is wrapped in internal function to save bytecode function _operatorGrid() internal view returns (OperatorGrid) { return OperatorGrid(LIDO_LOCATOR.operatorGrid()); } function _lazyOracle() internal view returns (LazyOracle) { return LazyOracle(LIDO_LOCATOR.lazyOracle()); } function _predepositGuarantee() internal view returns (IPredepositGuarantee) { return IPredepositGuarantee(LIDO_LOCATOR.predepositGuarantee()); } function _getSharesByPooledEth(uint256 _ether) internal view returns (uint256) { return LIDO.getSharesByPooledEth(_ether); } function _getPooledEthBySharesRoundUp(uint256 _shares) internal view returns (uint256) { return LIDO.getPooledEthBySharesRoundUp(_shares); } function _rebalanceExternalEtherToInternal(uint256 _ether, uint256 _amountOfShares) internal { LIDO.rebalanceExternalEtherToInternal{value: _ether}(_amountOfShares); } function _triggerVaultValidatorWithdrawals( address _vault, uint256 _value, bytes calldata _pubkeys, uint64[] memory _amountsInGwei, address _refundRecipient ) internal { IStakingVault(_vault).triggerValidatorWithdrawals{value: _value}(_pubkeys, _amountsInGwei, _refundRecipient); } function _withdrawFromVault(address _vault, address _recipient, uint256 _amount) internal { IStakingVault(_vault).withdraw(_recipient, _amount); } function _nodeOperator(address _vault) internal view returns (address) { return IStakingVault(_vault).nodeOperator(); } function _availableBalance(address _vault) internal view returns (uint256) { return IStakingVault(_vault).availableBalance(); } function _requireNotZero(uint256 _value) internal pure { if (_value == 0) revert ZeroArgument(); } function _requireNotZero(address _address) internal pure { if (_address == address(0)) revert ZeroAddress(); } function _requireSender(address _sender) internal view { if (msg.sender != _sender) revert NotAuthorized(); } function _requireSaneShareLimit(uint256 _shareLimit) internal view { uint256 maxSaneShareLimit = (LIDO.getTotalShares() * MAX_RELATIVE_SHARE_LIMIT_BP) / TOTAL_BASIS_POINTS; if (_shareLimit > maxSaneShareLimit) revert ShareLimitTooHigh(_shareLimit, maxSaneShareLimit); } function _requireConnected(VaultConnection storage _connection, address _vault) internal view { if (_connection.vaultIndex == 0) revert NotConnectedToHub(_vault); } function _requireFreshReport(address _vault, VaultRecord storage _record) internal view { if (!_isReportFresh(_record)) revert VaultReportStale(_vault); } function _isBeaconChainDepositsPaused(IStakingVault _vault) internal view returns (bool) { return _vault.beaconChainDepositsPaused(); } function _pauseBeaconChainDepositsIfNotAlready(IStakingVault _vault) internal { if (!_isBeaconChainDepositsPaused(_vault)) { _vault.pauseBeaconChainDeposits(); } } function _resumeBeaconChainDepositsIfNotAlready(IStakingVault _vault) internal { if (_isBeaconChainDepositsPaused(_vault)) { _vault.resumeBeaconChainDeposits(); } } // ----------------------------- // EVENTS // ----------------------------- /// @dev Warning! used by Accounting Oracle to calculate fees event VaultConnected( address indexed vault, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event VaultConnectionUpdated( address indexed vault, address indexed nodeOperator, uint256 shareLimit, uint256 reserveRatioBP, uint256 forcedRebalanceThresholdBP ); /// @dev Warning! used by Accounting Oracle to calculate fees event VaultFeesUpdated( address indexed vault, uint256 preInfraFeeBP, uint256 preLiquidityFeeBP, uint256 preReservationFeeBP, uint256 infraFeeBP, uint256 liquidityFeeBP, uint256 reservationFeeBP ); event VaultDisconnectInitiated(address indexed vault); event VaultDisconnectCompleted(address indexed vault); event VaultDisconnectAborted(address indexed vault, uint256 slashingReserve); event VaultReportApplied( address indexed vault, uint256 reportTimestamp, uint256 reportTotalValue, int256 reportInOutDelta, uint256 reportCumulativeLidoFees, uint256 reportLiabilityShares, uint256 reportMaxLiabilityShares, uint256 reportSlashingReserve ); /// @dev Warning! used by Accounting Oracle to calculate fees event MintedSharesOnVault(address indexed vault, uint256 amountOfShares, uint256 lockedAmount); /// @dev Warning! used by Accounting Oracle to calculate fees event BurnedSharesOnVault(address indexed vault, uint256 amountOfShares); /// @dev Warning! used by Accounting Oracle to calculate fees event VaultRebalanced(address indexed vault, uint256 sharesBurned, uint256 etherWithdrawn); event VaultInOutDeltaUpdated(address indexed vault, int256 inOutDelta); event ForcedValidatorExitTriggered(address indexed vault, bytes pubkeys, address refundRecipient); /** * @notice Emitted when the vault ownership is changed * @param vault The address of the vault * @param newOwner The address of the new owner * @param oldOwner The address of the old owner */ event VaultOwnershipTransferred(address indexed vault, address indexed newOwner, address indexed oldOwner); event LidoFeesSettled(address indexed vault, uint256 transferred, uint256 cumulativeLidoFees, uint256 settledLidoFees); event VaultRedemptionSharesUpdated(address indexed vault, uint256 redemptionShares); event BeaconChainDepositsPauseIntentSet(address indexed vault, bool pauseIntent); /// @dev Warning! used by Accounting Oracle to calculate fees event BadDebtSocialized(address indexed vaultDonor, address indexed vaultAcceptor, uint256 badDebtShares); /// @dev Warning! used by Accounting Oracle to calculate fees event BadDebtWrittenOffToBeInternalized(address indexed vault, uint256 badDebtShares); // ----------------------------- // ERRORS // ----------------------------- error PauseIntentAlreadySet(); error PauseIntentAlreadyUnset(); error AmountExceedsTotalValue(address vault, uint256 totalValue, uint256 withdrawAmount); error AmountExceedsWithdrawableValue(address vault, uint256 withdrawable, uint256 requested); error NoFundsForForceRebalance(address vault); error NoReasonForForceRebalance(address vault); error NoUnsettledLidoFeesToSettle(address vault); error NoFundsToSettleLidoFees(address vault, uint256 unsettledLidoFees); error VaultMintingCapacityExceeded( address vault, uint256 totalValue, uint256 liabilityShares, uint256 newRebalanceThresholdBP ); error InsufficientSharesToBurn(address vault, uint256 amount); error ShareLimitExceeded(address vault, uint256 expectedSharesAfterMint, uint256 shareLimit); error AlreadyConnected(address vault, uint256 index); error InsufficientStagedBalance(address vault); error NotConnectedToHub(address vault); error NotAuthorized(); error ZeroAddress(); error ZeroArgument(); error InvalidBasisPoints(uint256 valueBP, uint256 maxValueBP); error ShareLimitTooHigh(uint256 shareLimit, uint256 maxShareLimit); error InsufficientValue(address vault, uint256 etherToLock, uint256 maxLockableValue); error NoLiabilitySharesShouldBeLeft(address vault, uint256 liabilityShares); error NoUnsettledLidoFeesShouldBeLeft(address vault, uint256 unsettledLidoFees); error VaultOssified(address vault); error VaultInsufficientBalance(address vault, uint256 currentBalance, uint256 expectedBalance); error VaultReportStale(address vault); error PDGNotDepositor(address vault); error VaultHubNotPendingOwner(address vault); error VaultIsDisconnecting(address vault); error PartialValidatorWithdrawalNotAllowed(); error ForcedValidatorExitNotAllowed(); error BadDebtSocializationNotAllowed(); error VaultNotFactoryDeployed(address vault); }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line pragma solidity >=0.4.24 <0.9.0; interface IBurner { function REQUEST_BURN_MY_STETH_ROLE() external view returns (bytes32); function REQUEST_BURN_SHARES_ROLE() external view returns (bytes32); /** * Commit cover/non-cover burning requests and logs cover/non-cover shares amount just burnt. * * NB: The real burn enactment to be invoked after the call (via internal Lido._burnShares()) */ function commitSharesToBurn(uint256 _sharesToBurn) external; /** * Request burn shares */ function requestBurnShares(address _from, uint256 _sharesAmountToBurn) external; function requestBurnMyShares(uint256 _sharesAmountToBurn) external; /** * Returns the current amount of shares locked on the contract to be burnt. */ function getSharesRequestedToBurn() external view returns (uint256 coverShares, uint256 nonCoverShares); /** * Returns the total cover shares ever burnt. */ function getCoverSharesBurnt() external view returns (uint256); /** * Returns the total non-cover shares ever burnt. */ function getNonCoverSharesBurnt() external view returns (uint256); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.5.0; interface IDepositContract { function get_deposit_root() external view returns (bytes32 rootHash); function deposit( bytes calldata pubkey, // 48 bytes bytes calldata withdrawal_credentials, // 32 bytes bytes calldata signature, // 96 bytes bytes32 deposit_data_root ) external payable; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.5.0; interface IHashConsensus { function getIsMember(address addr) external view returns (bool); function getCurrentFrame() external view returns ( uint256 refSlot, uint256 reportProcessingDeadlineSlot ); function getChainConfig() external view returns ( uint256 slotsPerEpoch, uint256 secondsPerSlot, uint256 genesisTime ); function getFrameConfig() external view returns (uint256 initialEpoch, uint256 epochsPerFrame); function getInitialRefSlot() external view returns (uint256); }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.5.0; /** * Interface to connect AccountingOracle with LazyOracle and force type consistency */ interface ILazyOracle { function updateReportData( uint256 _vaultsDataTimestamp, uint256 _vaultsDataRefSlot, bytes32 _vaultsDataTreeRoot, string memory _vaultsDataReportCid ) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.8.0; import {IERC20} from "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import {IVersioned} from "contracts/common/interfaces/IVersioned.sol"; interface ILido is IERC20, IVersioned { function sharesOf(address) external view returns (uint256); function getSharesByPooledEth(uint256) external view returns (uint256); function getPooledEthByShares(uint256) external view returns (uint256); function getPooledEthBySharesRoundUp(uint256) external view returns (uint256); function transferSharesFrom(address, address, uint256) external returns (uint256); function transferShares(address, uint256) external returns (uint256); function rebalanceExternalEtherToInternal(uint256 _amountOfShares) external payable; function getTotalPooledEther() external view returns (uint256); function getExternalEther() external view returns (uint256); function getExternalShares() external view returns (uint256); function mintExternalShares(address, uint256) external; function burnExternalShares(uint256) external; function getTotalShares() external view returns (uint256); function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); function processClStateUpdate( uint256 _reportTimestamp, uint256 _preClValidators, uint256 _reportClValidators, uint256 _reportClBalance ) external; function collectRewardsAndProcessWithdrawals( uint256 _reportTimestamp, uint256 _reportClBalance, uint256 _adjustedPreCLBalance, uint256 _withdrawalsToWithdraw, uint256 _elRewardsToWithdraw, uint256 _lastWithdrawalRequestToFinalize, uint256 _simulatedShareRate, uint256 _etherToLockOnWithdrawalQueue ) external; function emitTokenRebase( uint256 _reportTimestamp, uint256 _timeElapsed, uint256 _preTotalShares, uint256 _preTotalEther, uint256 _postTotalShares, uint256 _postTotalEther, uint256 _postInternalShares, uint256 _postInternalEther, uint256 _sharesMintedAsFees ) external; function mintShares(address _recipient, uint256 _sharesAmount) external; function internalizeExternalBadDebt(uint256 _amountOfShares) external; }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity >=0.4.24 <0.9.0; interface ILidoLocator { function accountingOracle() external view returns(address); function depositSecurityModule() external view returns(address); function elRewardsVault() external view returns(address); function lido() external view returns(address); function oracleReportSanityChecker() external view returns(address); function burner() external view returns(address); function stakingRouter() external view returns(address); function treasury() external view returns(address); function validatorsExitBusOracle() external view returns(address); function withdrawalQueue() external view returns(address); function withdrawalVault() external view returns(address); function postTokenRebaseReceiver() external view returns(address); function oracleDaemonConfig() external view returns(address); function accounting() external view returns (address); function predepositGuarantee() external view returns (address); function wstETH() external view returns (address); function vaultHub() external view returns (address); function vaultFactory() external view returns (address); function lazyOracle() external view returns (address); function operatorGrid() external view returns (address); /// @notice Returns core Lido protocol component addresses in a single call /// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call function coreComponents() external view returns( address elRewardsVault, address oracleReportSanityChecker, address stakingRouter, address treasury, address withdrawalQueue, address withdrawalVault ); /// @notice Returns addresses of components involved in processing oracle reports in the Lido contract /// @dev This function provides a gas-efficient way to fetch multiple component addresses in a single call function oracleReportComponents() external view returns( address accountingOracle, address oracleReportSanityChecker, address burner, address withdrawalQueue, address postTokenRebaseReceiver, address stakingRouter, address vaultHub ); }
// SPDX-License-Identifier: GPL-3.0
// See contracts/COMPILERS.md
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity >=0.4.24;
interface IOssifiableProxy {
function proxy__upgradeTo(address newImplementation) external;
function proxy__changeAdmin(address newAdmin) external;
function proxy__getAdmin() external view returns (address);
function proxy__getImplementation() external view returns (address);
}// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md // solhint-disable-next-line pragma solidity >=0.4.24; interface IVersioned { /// @notice Returns the current contract version. function getContractVersion() external view returns (uint256); }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]> // SPDX-License-Identifier: MIT // Copied from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0457042d93d9dfd760dbaa06a4d2f1216fdbe297/contracts/utils/math/Math.sol // See contracts/COMPILERS.md // solhint-disable-next-line pragma solidity >=0.4.24 <0.9.0; library Math256 { /// @dev Returns the largest of two numbers. function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /// @dev Returns the smallest of two numbers. function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /// @dev Returns the largest of two numbers. function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /// @dev Returns the smallest of two numbers. function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /// @dev Returns the ceiling of the division of two numbers. /// /// This differs from standard division with `/` in that it rounds up instead /// of rounding down. function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /// @dev Returns absolute difference of two numbers. function absDiff(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a - b : b - a; } }
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>, Aragon // SPDX-License-Identifier: MIT // solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; library UnstructuredStorage { function getStorageBool(bytes32 position) internal view returns (bool data) { assembly { data := sload(position) } } function getStorageAddress(bytes32 position) internal view returns (address data) { assembly { data := sload(position) } } function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { assembly { data := sload(position) } } function getStorageUint256(bytes32 position) internal view returns (uint256 data) { assembly { data := sload(position) } } function setStorageBool(bytes32 position, bool data) internal { assembly { sstore(position, data) } } function setStorageAddress(bytes32 position, address data) internal { assembly { sstore(position, data) } } function setStorageBytes32(bytes32 position, bytes32 data) internal { assembly { sstore(position, data) } } function setStorageUint256(bytes32 position, uint256 data) internal { assembly { sstore(position, data) } } }
// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.9; import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol"; /** * @title PausableUntil * @notice allows to pause the contract for a specific duration or indefinitely */ abstract contract PausableUntil { using UnstructuredStorage for bytes32; /// Contract resume/pause control storage slot bytes32 internal constant RESUME_SINCE_TIMESTAMP_POSITION = keccak256("lido.PausableUntil.resumeSinceTimestamp"); /// Special value for the infinite pause uint256 public constant PAUSE_INFINITELY = type(uint256).max; /// @notice Emitted when paused by the `pauseFor` or `pauseUntil` call event Paused(uint256 duration); /// @notice Emitted when resumed by the `resume` call event Resumed(); error ZeroPauseDuration(); error PausedExpected(); error ResumedExpected(); error PauseUntilMustBeInFuture(); /// @notice Reverts if paused modifier whenResumed() { _checkResumed(); _; } /// @notice Returns whether the contract is paused function isPaused() public view returns (bool) { return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } /// @notice Returns one of: /// - PAUSE_INFINITELY if paused infinitely returns /// - the timestamp when the contract get resumed if paused for specific duration /// - some timestamp in past if not paused function getResumeSinceTimestamp() external view returns (uint256) { return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); } function _checkPaused() internal view { if (!isPaused()) { revert PausedExpected(); } } function _checkResumed() internal view { if (isPaused()) { revert ResumedExpected(); } } function _resume() internal { _checkPaused(); RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp); emit Resumed(); } function _pauseFor(uint256 _duration) internal { _checkResumed(); if (_duration == 0) revert ZeroPauseDuration(); uint256 resumeSince; if (_duration == PAUSE_INFINITELY) { resumeSince = PAUSE_INFINITELY; } else { resumeSince = block.timestamp + _duration; } _setPausedState(resumeSince); } function _pauseUntil(uint256 _pauseUntilInclusive) internal { _checkResumed(); if (_pauseUntilInclusive < block.timestamp) revert PauseUntilMustBeInFuture(); uint256 resumeSince; if (_pauseUntilInclusive != PAUSE_INFINITELY) { resumeSince = _pauseUntilInclusive + 1; } else { resumeSince = PAUSE_INFINITELY; } _setPausedState(resumeSince); } function _setPausedState(uint256 _resumeSince) internal { RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(_resumeSince); if (_resumeSince == PAUSE_INFINITELY) { emit Paused(PAUSE_INFINITELY); } else { emit Paused(_resumeSince - block.timestamp); } } }
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "@openzeppelin/contracts-v5.2/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
struct AccessControlStorage {
mapping(bytes32 role => RoleData) _roles;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;
function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
assembly {
$.slot := AccessControlStorageLocation
}
}
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
AccessControlStorage storage $ = _getAccessControlStorage();
bytes32 previousAdminRole = getRoleAdmin(role);
$._roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (!hasRole(role, account)) {
$._roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (hasRole(role, account)) {
$._roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "@openzeppelin/contracts-v5.2/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts-v5.2/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
struct AccessControlEnumerableStorage {
mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;
function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
assembly {
$.slot := AccessControlEnumerableStorageLocation
}
}
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool granted = super._grantRole(role, account);
if (granted) {
$._roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool revoked = super._revokeRole(role, account);
if (revoked) {
$._roleMembers[role].remove(account);
}
return revoked;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "@openzeppelin/contracts-v5.2/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165Upgradeable is Initializable, IERC165 {
function __ERC165_init() internal onlyInitializing {
}
function __ERC165_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: UNLICENSED // See contracts/COMPILERS.md // solhint-disable-next-line lido/fixed-compiler-version pragma solidity ^0.8.25; /// @notice Represents an external call to a specific address with an optional ETH transfer. /// @param target The address to call. /// @param value The amount of ETH (in wei) to transfer with the call, capped at approximately 7.9 billion ETH. /// @param payload The calldata payload sent to the target address. struct ExternalCall { address target; uint96 value; bytes payload; } /// @notice The info about the registered proposer and associated executor. /// @param account Address of the proposer. /// @param executor The address of the executor assigned to execute proposals submitted by the proposer. struct Proposer { address account; address executor; } interface IDualGovernance { function submitProposal( ExternalCall[] calldata calls, string calldata metadata ) external returns (uint256 proposalId); function scheduleProposal(uint256 proposalId) external; /// @notice Returns the information about all registered proposers. /// @return proposers An array of `Proposer` structs containing the data of all registered proposers. function getProposers() external view returns (Proposer[] memory proposers); event ProposalSubmitted(uint256 indexed id, address indexed executor, ExternalCall[] calls); }
// SPDX-License-Identifier: GPL-3.0
// See contracts/COMPILERS.md
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity >=0.4.24 <0.9.0;
interface IForwarder {
function execute(address _target, uint256 _ethValue, bytes memory _data) external payable;
function forward(bytes memory _evmScript) external;
}// SPDX-License-Identifier: GPL-3.0
// See contracts/COMPILERS.md
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity >=0.4.24 <0.9.0;
interface IVoting {
enum VotePhase {
Main,
Objection,
Closed
}
function getVote(uint256 _voteId)
external
view
returns (
bool open,
bool executed,
uint64 startDate,
uint64 snapshotBlock,
uint64 supportRequired,
uint64 minAcceptQuorum,
uint256 yea,
uint256 nay,
uint256 votingPower,
bytes memory script,
VotePhase phase
);
function newVote(
bytes calldata _executionScript,
string calldata _metadata,
bool, /* _castVote_deprecated */
bool /* _executesIfDecided_deprecated */
) external;
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.25;
/**
* @title CallsScriptBuilder
* @notice A library for building call scripts in a structured manner.
*
* This library provides utilities to construct Aragon EVM call scripts that can be used
* to execute multiple calls in a single transaction. It is particularly useful
* in governance systems where a series of actions need to be executed atomically.
*
* The library uses a specific format to encode the calls, which includes the
* target address, the length of the data, and the data itself. This format is
* compatible with the Aragon OS call script specification, see SPEC_ID.
*/
library CallsScriptBuilder {
// See https://github.com/aragon/aragonOS/pull/182
bytes4 internal constant SPEC_ID = 0x00000001;
struct Context {
bytes _result; // The encoded call script result
}
function getResult(Context memory self) internal pure returns (bytes memory) {
return self._result;
}
function create() internal pure returns (Context memory res) {
res._result = bytes.concat(SPEC_ID);
}
function create(address to, bytes memory data) internal pure returns (Context memory res) {
res = addCall(create(), to, data);
}
function addCall(Context memory self, address to, bytes memory data) internal pure returns (Context memory) {
self._result = bytes.concat(self._result, bytes20(to), bytes4(uint32(data.length)), data);
return self;
}
}// SPDX-License-Identifier: GPL-3.0
// See contracts/COMPILERS.md
// solhint-disable-next-line lido/fixed-compiler-version
pragma solidity ^0.8.25;
import {IForwarder} from "../interfaces/IForwarder.sol";
import {IVoting} from "../interfaces/IVoting.sol";
import {IDualGovernance, ExternalCall} from "../interfaces/IDualGovernance.sol";
import {CallsScriptBuilder} from "./CallScriptBuilder.sol";
/// @title OmnibusBase
/// @notice Abstract base contract for creating votes for the Aragon Voting.
///
/// @dev Originates from https://github.com/lidofinance/dual-governance/tree/98216fb2c9150b8111a14b06afd9d6e646f14c20/scripts/upgrade
/// @dev The OmnibusBase contract serves as a foundational layer for creating governance proposals
/// that are compatible with the Aragon Voting framework. It provides a structured approach
/// to define and execute a series of actions (vote items) within a single governance vote.
/// The contract leverages the CallsScriptBuilder library to construct EVM call scripts,
/// ensuring that all actions are executed atomically and in the specified order.
/// @dev This contract is designed to be extended and customized for specific governance
/// scenarios, allowing developers to define complex multi-step proposals that can be
/// executed within the Aragon governance ecosystem.
/// @dev Inheriting contracts are expected to implement the `getVoteItems()` function, which
/// outlines the specific actions to be included in the governance proposal. These actions
/// are encapsulated in the `VoteItem` struct, which includes a human-readable description
/// and the necessary EVM call data.
abstract contract OmnibusBase {
using CallsScriptBuilder for CallsScriptBuilder.Context;
struct ScriptCall {
address to;
bytes data;
}
/// @notice A structure that represents a single voting item in a governance proposal.
/// @dev This struct is designed to match the format required by the Lido scripts repository
/// for compatibility with the voting tooling.
/// @param description Human-readable description of the voting item.
/// @param call The EVM script call containing the target contract address and calldata.
struct VoteItem {
string description;
ScriptCall call;
}
IVoting internal immutable VOTING_CONTRACT;
IDualGovernance internal immutable DUAL_GOVERNANCE;
constructor(address voting, address dualGovernance) {
VOTING_CONTRACT = IVoting(voting);
DUAL_GOVERNANCE = IDualGovernance(dualGovernance);
}
/// @return VoteItem[] The list of items to be executed by Dual Governance.
function getVoteItems() public view virtual returns (VoteItem[] memory);
/// @return VoteItem[] The list of voting items to be executed by Aragon Voting.
function getVotingVoteItems() public view virtual returns (VoteItem[] memory);
/// @notice Converts all vote items to the Aragon-compatible EVMCallScript to validate against.
/// @param proposalMetadata The metadata of the proposal.
/// @return script A bytes containing encoded EVMCallScript.
function getEVMScript(string memory proposalMetadata) public view returns (bytes memory) {
VoteItem[] memory dgVoteItems = this.getVoteItems();
ExternalCall[] memory dgCalls = new ExternalCall[](dgVoteItems.length);
for (uint256 i = 0; i < dgVoteItems.length; i++) {
dgCalls[i] = ExternalCall({
target: dgVoteItems[i].call.to,
value: 0,
payload: dgVoteItems[i].call.data
});
}
CallsScriptBuilder.Context memory scriptBuilder = CallsScriptBuilder.create();
scriptBuilder.addCall(address(DUAL_GOVERNANCE), abi.encodeCall(IDualGovernance.submitProposal, (dgCalls, proposalMetadata)));
VoteItem[] memory votingVoteItems = this.getVotingVoteItems();
for (uint256 i = 0; i < votingVoteItems.length; i++) {
scriptBuilder.addCall(votingVoteItems[i].call.to, votingVoteItems[i].call.data);
}
return scriptBuilder.getResult();
}
/// @notice Returns the bytecode for creating a new vote on the Aragon Voting contract.
/// @param description The description of the vote.
/// @param proposalMetadata The metadata of the proposal.
/// @return newVoteBytecode The bytecode for creating a new vote.
function getNewVoteCallBytecode(string memory description, string memory proposalMetadata) external view returns (bytes memory newVoteBytecode) {
newVoteBytecode = CallsScriptBuilder.create(
address(VOTING_CONTRACT), abi.encodeCall(VOTING_CONTRACT.newVote, (getEVMScript(proposalMetadata), description, false, false))
)._result;
}
/// @notice Validates the specific vote on Aragon Voting contract.
/// @param voteId The ID of the vote.
/// @param proposalMetadata The metadata of the proposal.
/// @return A boolean value indicating whether the vote is valid.
function isValidVoteScript(uint256 voteId, string memory proposalMetadata) external view returns (bool) {
( /*open*/
, /*executed*/
, /*startDate*/
, /*snapshotBlock*/
, /*supportRequired*/
, /*minAcceptQuorum*/
, /*yea*/
, /*nay*/
, /*votingPower*/
,
bytes memory script,
/*phase*/
) = VOTING_CONTRACT.getVote(voteId);
return keccak256(script) == keccak256(getEVMScript(proposalMetadata));
}
function _votingCall(address target, bytes memory data) internal pure returns (ScriptCall memory) {
return ScriptCall(target, data);
}
function _forwardCall(
address forwarder,
address target,
bytes memory data
) internal pure returns (ScriptCall memory) {
return ScriptCall(
forwarder, abi.encodeCall(IForwarder.forward, (CallsScriptBuilder.create(target, data).getResult()))
);
}
}// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.25;
import {IAccessControlEnumerable} from "@openzeppelin/contracts-v4.4/access/AccessControlEnumerable.sol";
import {ILidoLocator} from "contracts/common/interfaces/ILidoLocator.sol";
interface IVaultsAdapter {
function evmScriptExecutor() external view returns (address);
}
interface IStakingRouter is IAccessControlEnumerable {
struct StakingModule {
uint24 id;
address stakingModuleAddress;
uint16 stakingModuleFee;
uint16 treasuryFee;
uint16 stakeShareLimit;
uint8 status;
string name;
uint64 lastDepositAt;
uint256 lastDepositBlock;
uint256 exitedValidatorsCount;
uint16 priorityExitShareThreshold;
uint64 maxDepositsPerBlock;
uint64 minDepositBlockDistance;
}
function getStakingModules() external view returns (StakingModule[] memory res);
}
interface ICSModule {
function accounting() external view returns (address);
}
/**
* @title V3UpgradeAddresses
* @notice Stores immutable addresses required for the V3 upgrade process.
* This contract centralizes address management for V3Template and V3VoteScript.
*/
contract V3Addresses {
struct V3AddressesParams {
// Old implementations
address oldLocatorImpl;
address oldLidoImpl;
address oldAccountingOracleImpl;
address oldTokenRateNotifier;
// New implementations
address newLocatorImpl;
address newLidoImpl;
address newAccountingOracleImpl;
address newTokenRateNotifier;
// New fancy proxy and blueprint contracts
address upgradeableBeacon;
address stakingVaultImpl;
address dashboardImpl;
address gateSealForVaults;
// Existing proxies and contracts
address kernel;
bytes32 lidoAppId;
address agent;
address locator;
address voting;
address dualGovernance;
address acl;
address resealManager;
// EasyTrack addresses
address easyTrack;
address vaultsAdapter;
// EasyTrack new factories
address etfAlterTiersInOperatorGrid;
address etfRegisterGroupsInOperatorGrid;
address etfRegisterTiersInOperatorGrid;
address etfUpdateGroupsShareLimitInOperatorGrid;
address etfSetJailStatusInOperatorGrid;
address etfUpdateVaultsFeesInOperatorGrid;
address etfForceValidatorExitsInVaultHub;
address etfSocializeBadDebtInVaultHub;
}
string public constant CURATED_MODULE_NAME = "curated-onchain-v1";
string public constant SIMPLE_DVT_MODULE_NAME = "SimpleDVT";
string public constant CSM_MODULE_NAME = "Community Staking";
//
// -------- Pre-upgrade old contracts --------
//
address public immutable OLD_LOCATOR_IMPL;
address public immutable OLD_BURNER;
address public immutable OLD_ACCOUNTING_ORACLE_IMPL;
address public immutable OLD_LIDO_IMPL;
address public immutable OLD_TOKEN_RATE_NOTIFIER;
//
// -------- Upgraded contracts --------
//
address public immutable LOCATOR;
address public immutable NEW_LOCATOR_IMPL;
address public immutable LIDO;
address public immutable ACCOUNTING_ORACLE;
address public immutable BURNER;
address public immutable ORACLE_REPORT_SANITY_CHECKER;
address public immutable NEW_LIDO_IMPL;
address public immutable NEW_ACCOUNTING_ORACLE_IMPL;
address public immutable NEW_TOKEN_RATE_NOTIFIER;
//
// -------- New V3 contracts --------
//
address public immutable ACCOUNTING;
address payable public immutable VAULT_HUB;
address public immutable PREDEPOSIT_GUARANTEE;
address public immutable OPERATOR_GRID;
address public immutable LAZY_ORACLE;
address public immutable VAULT_FACTORY;
address public immutable UPGRADEABLE_BEACON;
address public immutable STAKING_VAULT_IMPL;
address public immutable DASHBOARD_IMPL;
address public immutable GATE_SEAL;
//
// -------- EasyTrack addresses --------
//
address public immutable EASY_TRACK;
address public immutable EVM_SCRIPT_EXECUTOR;
address public immutable VAULTS_ADAPTER;
// ETF = EasyTrack Factory
address public immutable ETF_ALTER_TIERS_IN_OPERATOR_GRID;
address public immutable ETF_REGISTER_GROUPS_IN_OPERATOR_GRID;
address public immutable ETF_REGISTER_TIERS_IN_OPERATOR_GRID;
address public immutable ETF_SET_JAIL_STATUS_IN_OPERATOR_GRID;
address public immutable ETF_SOCIALIZE_BAD_DEBT_IN_VAULT_HUB;
address public immutable ETF_UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID;
address public immutable ETF_UPDATE_VAULTS_FEES_IN_OPERATOR_GRID;
address public immutable ETF_FORCE_VALIDATOR_EXITS_IN_VAULT_HUB;
//
// -------- Unchanged contracts --------
//
address public immutable KERNEL;
bytes32 public immutable LIDO_APP_ID;
address public immutable AGENT;
address public immutable VOTING;
address public immutable DUAL_GOVERNANCE;
address public immutable ACL;
address public immutable EL_REWARDS_VAULT;
address public immutable STAKING_ROUTER;
address public immutable VALIDATORS_EXIT_BUS_ORACLE;
address public immutable WITHDRAWAL_QUEUE;
address public immutable WSTETH;
address public immutable NODE_OPERATORS_REGISTRY;
address public immutable SIMPLE_DVT;
address public immutable CSM_ACCOUNTING;
address public immutable ORACLE_DAEMON_CONFIG;
address public immutable RESEAL_MANAGER;
constructor(
V3AddressesParams memory params
) {
if (params.newLocatorImpl == params.oldLocatorImpl) {
revert NewAndOldLocatorImplementationsMustBeDifferent();
}
if (params.oldTokenRateNotifier == params.newTokenRateNotifier) {
revert OldAndNewTokenRateNotifiersMustBeDifferent();
}
//
// Set directly from passed parameters
//
ILidoLocator newLocatorImpl = ILidoLocator(params.newLocatorImpl);
OLD_LOCATOR_IMPL = params.oldLocatorImpl;
OLD_ACCOUNTING_ORACLE_IMPL = params.oldAccountingOracleImpl;
OLD_LIDO_IMPL = params.oldLidoImpl;
OLD_TOKEN_RATE_NOTIFIER = params.oldTokenRateNotifier;
LOCATOR = params.locator;
NEW_LOCATOR_IMPL = params.newLocatorImpl;
NEW_LIDO_IMPL = params.newLidoImpl;
NEW_ACCOUNTING_ORACLE_IMPL = params.newAccountingOracleImpl;
NEW_TOKEN_RATE_NOTIFIER = params.newTokenRateNotifier;
KERNEL = params.kernel;
LIDO_APP_ID = params.lidoAppId;
AGENT = params.agent;
VOTING = params.voting;
DUAL_GOVERNANCE = params.dualGovernance;
ACL = params.acl;
UPGRADEABLE_BEACON = params.upgradeableBeacon;
STAKING_VAULT_IMPL = params.stakingVaultImpl;
DASHBOARD_IMPL = params.dashboardImpl;
GATE_SEAL = params.gateSealForVaults;
EVM_SCRIPT_EXECUTOR = IVaultsAdapter(params.vaultsAdapter).evmScriptExecutor();
EASY_TRACK = params.easyTrack;
VAULTS_ADAPTER = params.vaultsAdapter;
ETF_ALTER_TIERS_IN_OPERATOR_GRID = params.etfAlterTiersInOperatorGrid;
ETF_REGISTER_GROUPS_IN_OPERATOR_GRID = params.etfRegisterGroupsInOperatorGrid;
ETF_REGISTER_TIERS_IN_OPERATOR_GRID = params.etfRegisterTiersInOperatorGrid;
ETF_SET_JAIL_STATUS_IN_OPERATOR_GRID = params.etfSetJailStatusInOperatorGrid;
ETF_SOCIALIZE_BAD_DEBT_IN_VAULT_HUB = params.etfSocializeBadDebtInVaultHub;
ETF_UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID = params.etfUpdateGroupsShareLimitInOperatorGrid;
ETF_UPDATE_VAULTS_FEES_IN_OPERATOR_GRID = params.etfUpdateVaultsFeesInOperatorGrid;
ETF_FORCE_VALIDATOR_EXITS_IN_VAULT_HUB = params.etfForceValidatorExitsInVaultHub;
//
// Discovered via other contracts
//
OLD_BURNER = ILidoLocator(params.oldLocatorImpl).burner();
LIDO = newLocatorImpl.lido();
ACCOUNTING_ORACLE = newLocatorImpl.accountingOracle();
BURNER = newLocatorImpl.burner();
ORACLE_REPORT_SANITY_CHECKER = newLocatorImpl.oracleReportSanityChecker();
ACCOUNTING = newLocatorImpl.accounting();
VAULT_HUB = payable(newLocatorImpl.vaultHub());
VAULT_FACTORY = newLocatorImpl.vaultFactory();
PREDEPOSIT_GUARANTEE = newLocatorImpl.predepositGuarantee();
OPERATOR_GRID = newLocatorImpl.operatorGrid();
LAZY_ORACLE = newLocatorImpl.lazyOracle();
EL_REWARDS_VAULT = newLocatorImpl.elRewardsVault();
STAKING_ROUTER = newLocatorImpl.stakingRouter();
VALIDATORS_EXIT_BUS_ORACLE = newLocatorImpl.validatorsExitBusOracle();
WITHDRAWAL_QUEUE = newLocatorImpl.withdrawalQueue();
WSTETH = newLocatorImpl.wstETH();
ORACLE_DAEMON_CONFIG = newLocatorImpl.oracleDaemonConfig();
RESEAL_MANAGER = params.resealManager;
{
// Retrieve contracts with burner allowances to migrate: NOR, SDVT and CSM ACCOUNTING
bytes32 curatedHash = _hash(CURATED_MODULE_NAME);
bytes32 simpleDvtHash = _hash(SIMPLE_DVT_MODULE_NAME);
bytes32 csmHash = _hash(CSM_MODULE_NAME);
address nodeOperatorsRegistry;
address simpleDvt;
address csmAccounting;
IStakingRouter.StakingModule[] memory stakingModules = IStakingRouter(STAKING_ROUTER).getStakingModules();
for (uint256 i = 0; i < stakingModules.length; i++) {
bytes32 nameHash = _hash(stakingModules[i].name);
if (nameHash == curatedHash) {
nodeOperatorsRegistry = stakingModules[i].stakingModuleAddress;
} else if (nameHash == simpleDvtHash) {
simpleDvt = stakingModules[i].stakingModuleAddress;
} else if (nameHash == csmHash) {
csmAccounting = ICSModule(stakingModules[i].stakingModuleAddress).accounting();
}
}
if (nodeOperatorsRegistry == address(0)) revert StakingModuleNotFound(CURATED_MODULE_NAME);
if (simpleDvt == address(0)) revert StakingModuleNotFound(SIMPLE_DVT_MODULE_NAME);
if (csmAccounting == address(0)) revert StakingModuleNotFound(CSM_MODULE_NAME);
NODE_OPERATORS_REGISTRY = nodeOperatorsRegistry;
SIMPLE_DVT = simpleDvt;
CSM_ACCOUNTING = csmAccounting;
}
}
function _hash(string memory input) internal pure returns (bytes32) {
return keccak256(bytes(input));
}
error NewAndOldLocatorImplementationsMustBeDifferent();
error OldAndNewTokenRateNotifiersMustBeDifferent();
error StakingModuleNotFound(string moduleName);
}// SPDX-FileCopyrightText: 2025 Lido <[email protected]> // SPDX-License-Identifier: GPL-3.0 // See contracts/COMPILERS.md pragma solidity 0.8.25; import {IAccessControlEnumerable} from "@openzeppelin/contracts-v4.4/access/AccessControlEnumerable.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts-v5.2/proxy/beacon/UpgradeableBeacon.sol"; import {IBurner as IBurnerWithoutAccessControl} from "contracts/common/interfaces/IBurner.sol"; import {IVersioned} from "contracts/common/interfaces/IVersioned.sol"; import {IOssifiableProxy} from "contracts/common/interfaces/IOssifiableProxy.sol"; import {ILido} from "contracts/common/interfaces/ILido.sol"; import {VaultHub} from "contracts/0.8.25/vaults/VaultHub.sol"; import {VaultFactory} from "contracts/0.8.25/vaults/VaultFactory.sol"; import {OperatorGrid} from "contracts/0.8.25/vaults/OperatorGrid.sol"; import {PausableUntilWithRoles} from "contracts/0.8.25/utils/PausableUntilWithRoles.sol"; import {V3Addresses} from "./V3Addresses.sol"; interface IBaseOracle is IAccessControlEnumerable, IVersioned { function getConsensusContract() external view returns (address); } interface IEasyTrack { function getEVMScriptFactories() external view returns (address[] memory); } interface IStakingRouter is IAccessControlEnumerable { function REPORT_REWARDS_MINTED_ROLE() external view returns (bytes32); } interface IBurner is IBurnerWithoutAccessControl, IAccessControlEnumerable { function REQUEST_BURN_SHARES_ROLE() external view returns (bytes32); function isMigrationAllowed() external view returns (bool); } interface ILidoWithFinalizeUpgrade is ILido { function finalizeUpgrade_v3(address _oldBurner, address[] calldata _contractsWithBurnerAllowances, uint256 _initialMaxExternalRatioBP) external; } interface IAccountingOracle is IBaseOracle { function finalizeUpgrade_v4(uint256 consensusVersion) external; } interface IAragonKernel { function getApp(bytes32 _namespace, bytes32 _appId) external view returns (address); function APP_BASES_NAMESPACE() external view returns (bytes32); } interface IWithdrawalsManagerProxy { function proxy_getAdmin() external view returns (address); function implementation() external view returns (address); } interface IOracleReportSanityChecker is IAccessControlEnumerable { function ALL_LIMITS_MANAGER_ROLE() external view returns (bytes32); function EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE() external view returns (bytes32); function APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE() external view returns (bytes32); function ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE() external view returns (bytes32); function SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE() external view returns (bytes32); function MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE() external view returns (bytes32); function MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE() external view returns (bytes32); function MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE() external view returns (bytes32); function REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE() external view returns (bytes32); function MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE() external view returns (bytes32); function SECOND_OPINION_MANAGER_ROLE() external view returns (bytes32); function INITIAL_SLASHING_AND_PENALTIES_MANAGER_ROLE() external view returns (bytes32); } interface ITokenRateNotifier { function owner() external view returns (address); function observers(uint256 index) external view returns (address); function observersLength() external view returns (uint256); } interface ILazyOracle { function UPDATE_SANITY_PARAMS_ROLE() external view returns (bytes32); } /** * @title Lido V3 Upgrade Template * * @dev Must be used by means of two calls: * - `startUpgrade()` before upgrading LidoLocator and before everything else * - `finishUpgrade()` as the last step of the upgrade */ contract V3Template is V3Addresses { // // Events // event UpgradeStarted(); event UpgradeFinished(); // // -------- Constants -------- // uint256 public constant EXPECTED_FINAL_LIDO_VERSION = 3; uint256 public constant EXPECTED_FINAL_ACCOUNTING_ORACLE_VERSION = 4; uint256 public constant EXPECTED_FINAL_ACCOUNTING_ORACLE_CONSENSUS_VERSION = 5; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; // Timestamp since which startUpgrade() // This behavior is introduced to disarm the template if the upgrade voting creation or enactment // didn't happen in proper time period uint256 public immutable EXPIRE_SINCE_INCLUSIVE; // Initial value of upgradeBlockNumber storage variable uint256 public constant UPGRADE_NOT_STARTED = 0; uint256 public constant INFINITE_ALLOWANCE = type(uint256).max; // // Structured storage // uint256 public upgradeBlockNumber = UPGRADE_NOT_STARTED; bool public isUpgradeFinished; uint256 public initialOldBurnerStethSharesBalance; uint256 public initialTotalShares; uint256 public initialTotalPooledEther; address[] public contractsWithBurnerAllowances; uint256 public immutable INITIAL_MAX_EXTERNAL_RATIO_BP; // // Slots for transient storage // // Slot for the upgrade started flag // keccak256("V3Template.upgradeStartedFlag") bytes32 public constant UPGRADE_STARTED_SLOT = 0x058d69f67a3d86c424c516d23a070ff8bed34431617274caa2049bd702675e3f; /// @param _params Params required to initialize the addresses contract /// @param _expireSinceInclusive Unix timestamp after which upgrade actions revert /// @param _initialMaxExternalRatioBP Initial maximum external ratio in basis points constructor(V3AddressesParams memory _params, uint256 _expireSinceInclusive, uint256 _initialMaxExternalRatioBP) V3Addresses(_params) { EXPIRE_SINCE_INCLUSIVE = _expireSinceInclusive; INITIAL_MAX_EXTERNAL_RATIO_BP = _initialMaxExternalRatioBP; contractsWithBurnerAllowances.push(WITHDRAWAL_QUEUE); // NB: NOR and SIMPLE_DVT allowances are set to 0 in TW upgrade, so they are not migrated contractsWithBurnerAllowances.push(CSM_ACCOUNTING); } /// @notice Must be called before LidoLocator is upgraded function startUpgrade() external { if (msg.sender != AGENT) revert OnlyAgentCanUpgrade(); if (block.timestamp >= EXPIRE_SINCE_INCLUSIVE) revert Expired(); if (isUpgradeFinished) revert UpgradeAlreadyFinished(); if (_isStartCalledInThisTx()) revert StartAlreadyCalledInThisTx(); if (upgradeBlockNumber != UPGRADE_NOT_STARTED) revert UpgradeAlreadyStarted(); assembly { tstore(UPGRADE_STARTED_SLOT, 1) } upgradeBlockNumber = block.number; initialTotalShares = ILidoWithFinalizeUpgrade(LIDO).getTotalShares(); initialTotalPooledEther = ILidoWithFinalizeUpgrade(LIDO).getTotalPooledEther(); _assertPreUpgradeState(); // Save initial state for the check after burner migration initialOldBurnerStethSharesBalance = ILidoWithFinalizeUpgrade(LIDO).sharesOf(OLD_BURNER); emit UpgradeStarted(); } function finishUpgrade() external { if (msg.sender != AGENT) revert OnlyAgentCanUpgrade(); if (isUpgradeFinished) revert UpgradeAlreadyFinished(); if (!_isStartCalledInThisTx()) revert StartAndFinishMustBeInSameTx(); isUpgradeFinished = true; ILidoWithFinalizeUpgrade(LIDO).finalizeUpgrade_v3(OLD_BURNER, contractsWithBurnerAllowances, INITIAL_MAX_EXTERNAL_RATIO_BP); IAccountingOracle(ACCOUNTING_ORACLE).finalizeUpgrade_v4(EXPECTED_FINAL_ACCOUNTING_ORACLE_CONSENSUS_VERSION); _assertPostUpgradeState(); emit UpgradeFinished(); } function _assertPreUpgradeState() internal view { // Check initial implementations of the proxies to be upgraded _assertProxyImplementation(IOssifiableProxy(LOCATOR), OLD_LOCATOR_IMPL); _assertProxyImplementation(IOssifiableProxy(ACCOUNTING_ORACLE), OLD_ACCOUNTING_ORACLE_IMPL); _assertAragonKernelImplementation(IAragonKernel(KERNEL), OLD_LIDO_IMPL); // Check allowances of the old burner address[] memory contractsWithBurnerAllowances_ = contractsWithBurnerAllowances; for (uint256 i = 0; i < contractsWithBurnerAllowances_.length; ++i) { if (ILidoWithFinalizeUpgrade(LIDO).allowance(contractsWithBurnerAllowances_[i], OLD_BURNER) != INFINITE_ALLOWANCE) { revert IncorrectBurnerAllowance(contractsWithBurnerAllowances_[i], OLD_BURNER); } } if (ILidoWithFinalizeUpgrade(LIDO).allowance(NODE_OPERATORS_REGISTRY, OLD_BURNER) != 0) { revert IncorrectBurnerAllowance(NODE_OPERATORS_REGISTRY, OLD_BURNER); } if (ILidoWithFinalizeUpgrade(LIDO).allowance(SIMPLE_DVT, OLD_BURNER) != 0) { revert IncorrectBurnerAllowance(SIMPLE_DVT, OLD_BURNER); } if (!IBurner(BURNER).isMigrationAllowed()) revert BurnerMigrationNotAllowed(); } function _assertPostUpgradeState() internal view { if ( ILidoWithFinalizeUpgrade(LIDO).getTotalShares() != initialTotalShares || ILidoWithFinalizeUpgrade(LIDO).getTotalPooledEther() != initialTotalPooledEther ) { revert TotalSharesOrPooledEtherChanged(); } _assertProxyImplementation(IOssifiableProxy(LOCATOR), NEW_LOCATOR_IMPL); _assertProxyImplementation(IOssifiableProxy(ACCOUNTING_ORACLE), NEW_ACCOUNTING_ORACLE_IMPL); _assertAragonKernelImplementation(IAragonKernel(KERNEL), NEW_LIDO_IMPL); _assertContractVersion(IVersioned(LIDO), EXPECTED_FINAL_LIDO_VERSION); _assertContractVersion(IVersioned(ACCOUNTING_ORACLE), EXPECTED_FINAL_ACCOUNTING_ORACLE_VERSION); _assertFinalACL(); _checkTokenRateNotifierMigratedCorrectly(); _checkBurnerMigratedCorrectly(); if (VaultFactory(VAULT_FACTORY).BEACON() != UPGRADEABLE_BEACON) { revert IncorrectVaultFactoryBeacon(VAULT_FACTORY, UPGRADEABLE_BEACON); } if (VaultFactory(VAULT_FACTORY).DASHBOARD_IMPL() != DASHBOARD_IMPL) { revert IncorrectVaultFactoryDashboardImplementation(VAULT_FACTORY, DASHBOARD_IMPL); } if (UpgradeableBeacon(UPGRADEABLE_BEACON).owner() != AGENT) { revert IncorrectUpgradeableBeaconOwner(UPGRADEABLE_BEACON, AGENT); } if (UpgradeableBeacon(UPGRADEABLE_BEACON).implementation() != STAKING_VAULT_IMPL) { revert IncorrectUpgradeableBeaconImplementation(UPGRADEABLE_BEACON, STAKING_VAULT_IMPL); } } function _assertFinalACL() internal view { // Burner bytes32 requestBurnSharesRole = IBurner(BURNER).REQUEST_BURN_SHARES_ROLE(); _assertZeroOZRoleHolders(OLD_BURNER, requestBurnSharesRole); _assertProxyAdmin(IOssifiableProxy(BURNER), AGENT); _assertSingleOZRoleHolder(BURNER, DEFAULT_ADMIN_ROLE, AGENT); { address[] memory holders = new address[](2); holders[0] = ACCOUNTING; holders[1] = CSM_ACCOUNTING; _assertOZRoleHolders(BURNER, requestBurnSharesRole, holders); } // VaultHub _assertProxyAdmin(IOssifiableProxy(VAULT_HUB), AGENT); _assertSingleOZRoleHolder(VAULT_HUB, DEFAULT_ADMIN_ROLE, AGENT); _assertSingleOZRoleHolder(VAULT_HUB, VaultHub(VAULT_HUB).VALIDATOR_EXIT_ROLE(), VAULTS_ADAPTER); _assertSingleOZRoleHolder(VAULT_HUB, VaultHub(VAULT_HUB).BAD_DEBT_MASTER_ROLE(), VAULTS_ADAPTER); _assertZeroOZRoleHolders(VAULT_HUB, VaultHub(VAULT_HUB).REDEMPTION_MASTER_ROLE()); _assertZeroOZRoleHolders(VAULT_HUB, VaultHub(VAULT_HUB).VAULT_MASTER_ROLE()); _assertTwoOZRoleHolders(VAULT_HUB, PausableUntilWithRoles(VAULT_HUB).PAUSE_ROLE(), GATE_SEAL, RESEAL_MANAGER); _assertSingleOZRoleHolder(VAULT_HUB, PausableUntilWithRoles(VAULT_HUB).RESUME_ROLE(), RESEAL_MANAGER); // OperatorGrid _assertProxyAdmin(IOssifiableProxy(OPERATOR_GRID), AGENT); _assertSingleOZRoleHolder(OPERATOR_GRID, DEFAULT_ADMIN_ROLE, AGENT); _assertTwoOZRoleHolders(OPERATOR_GRID, OperatorGrid(OPERATOR_GRID).REGISTRY_ROLE(), EVM_SCRIPT_EXECUTOR, VAULTS_ADAPTER); // LazyOracle _assertProxyAdmin(IOssifiableProxy(LAZY_ORACLE), AGENT); _assertSingleOZRoleHolder(LAZY_ORACLE, DEFAULT_ADMIN_ROLE, AGENT); _assertZeroOZRoleHolders(LAZY_ORACLE, ILazyOracle(LAZY_ORACLE).UPDATE_SANITY_PARAMS_ROLE()); // AccountingOracle _assertProxyAdmin(IOssifiableProxy(ACCOUNTING_ORACLE), AGENT); _assertSingleOZRoleHolder(ACCOUNTING_ORACLE, DEFAULT_ADMIN_ROLE, AGENT); // OracleReportSanityChecker IOracleReportSanityChecker checker = IOracleReportSanityChecker(ORACLE_REPORT_SANITY_CHECKER); _assertSingleOZRoleHolder(ORACLE_REPORT_SANITY_CHECKER, DEFAULT_ADMIN_ROLE, AGENT); bytes32[12] memory roles = [ checker.ALL_LIMITS_MANAGER_ROLE(), checker.EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), checker.APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), checker.ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE(), checker.SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE(), checker.MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE(), checker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), checker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE(), checker.REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE(), checker.MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE(), checker.SECOND_OPINION_MANAGER_ROLE(), checker.INITIAL_SLASHING_AND_PENALTIES_MANAGER_ROLE() ]; for (uint256 i = 0; i < roles.length; ++i) { _assertZeroOZRoleHolders(ORACLE_REPORT_SANITY_CHECKER, roles[i]); } // Accounting _assertProxyAdmin(IOssifiableProxy(ACCOUNTING), AGENT); // PredepositGuarantee _assertProxyAdmin(IOssifiableProxy(PREDEPOSIT_GUARANTEE), AGENT); _assertSingleOZRoleHolder(PREDEPOSIT_GUARANTEE, DEFAULT_ADMIN_ROLE, AGENT); _assertTwoOZRoleHolders(PREDEPOSIT_GUARANTEE, PausableUntilWithRoles(PREDEPOSIT_GUARANTEE).PAUSE_ROLE(), GATE_SEAL, RESEAL_MANAGER); _assertSingleOZRoleHolder(PREDEPOSIT_GUARANTEE, PausableUntilWithRoles(PREDEPOSIT_GUARANTEE).RESUME_ROLE(), RESEAL_MANAGER); // StakingRouter bytes32 reportRewardsMintedRole = IStakingRouter(STAKING_ROUTER).REPORT_REWARDS_MINTED_ROLE(); _assertSingleOZRoleHolder(STAKING_ROUTER, reportRewardsMintedRole, ACCOUNTING); _assertEasyTrackFactoriesAdded(); } function _assertEasyTrackFactoriesAdded() internal view { IEasyTrack easyTrack = IEasyTrack(EASY_TRACK); address[] memory factories = easyTrack.getEVMScriptFactories(); // The expected order of the last 8 EasyTrack factories address[8] memory expectedFactories = [ ETF_ALTER_TIERS_IN_OPERATOR_GRID, ETF_REGISTER_GROUPS_IN_OPERATOR_GRID, ETF_REGISTER_TIERS_IN_OPERATOR_GRID, ETF_UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID, ETF_SET_JAIL_STATUS_IN_OPERATOR_GRID, ETF_UPDATE_VAULTS_FEES_IN_OPERATOR_GRID, ETF_FORCE_VALIDATOR_EXITS_IN_VAULT_HUB, ETF_SOCIALIZE_BAD_DEBT_IN_VAULT_HUB ]; uint256 numFactories = factories.length; if (numFactories < expectedFactories.length) { revert UnexpectedEasyTrackFactories(); } for (uint256 i = 0; i < expectedFactories.length; ++i) { if (factories[numFactories - expectedFactories.length + i] != expectedFactories[i]) { revert UnexpectedEasyTrackFactories(); } } } function _checkTokenRateNotifierMigratedCorrectly() internal view { ITokenRateNotifier oldNotifier = ITokenRateNotifier(OLD_TOKEN_RATE_NOTIFIER); ITokenRateNotifier newNotifier = ITokenRateNotifier(NEW_TOKEN_RATE_NOTIFIER); if (newNotifier.owner() != AGENT) { revert IncorrectTokenRateNotifierOwnerMigration(NEW_TOKEN_RATE_NOTIFIER, AGENT); } if (oldNotifier.observersLength() != newNotifier.observersLength()) { revert IncorrectTokenRateNotifierObserversLengthMigration(); } for (uint256 i = 0; i < oldNotifier.observersLength(); i++) { if (oldNotifier.observers(i) != newNotifier.observers(i)) { revert IncorrectTokenRateNotifierObserversMigration(); } } } function _checkBurnerMigratedCorrectly() internal view { if (IBurner(OLD_BURNER).getCoverSharesBurnt() != IBurner(BURNER).getCoverSharesBurnt()) { revert IncorrectBurnerSharesMigration("Cover shares burnt mismatch"); } if (IBurner(OLD_BURNER).getNonCoverSharesBurnt() != IBurner(BURNER).getNonCoverSharesBurnt()) { revert IncorrectBurnerSharesMigration("Non-cover shares burnt mismatch"); } (uint256 oldCoverShares, uint256 oldNonCoverShares) = IBurner(OLD_BURNER).getSharesRequestedToBurn(); (uint256 newCoverShares, uint256 newNonCoverShares) = IBurner(BURNER).getSharesRequestedToBurn(); if (oldCoverShares != newCoverShares) { revert IncorrectBurnerSharesMigration("Cover shares requested to burn mismatch"); } if (oldNonCoverShares != newNonCoverShares) { revert IncorrectBurnerSharesMigration("Non-cover shares requested to burn mismatch"); } if (ILidoWithFinalizeUpgrade(LIDO).balanceOf(OLD_BURNER) != 0) { revert IncorrectBurnerSharesMigration("Old burner stETH balance is not zero"); } if (ILidoWithFinalizeUpgrade(LIDO).sharesOf(BURNER) != initialOldBurnerStethSharesBalance) { revert IncorrectBurnerSharesMigration("New burner stETH balance mismatch"); } if (IBurner(BURNER).isMigrationAllowed()) { revert IncorrectBurnerSharesMigration("Burner migration is still allowed"); } address[] memory contractsWithBurnerAllowances_ = contractsWithBurnerAllowances; for (uint256 i = 0; i < contractsWithBurnerAllowances_.length; i++) { if (ILidoWithFinalizeUpgrade(LIDO).allowance(contractsWithBurnerAllowances_[i], OLD_BURNER) != 0) { revert IncorrectBurnerAllowance(contractsWithBurnerAllowances_[i], OLD_BURNER); } if (ILidoWithFinalizeUpgrade(LIDO).allowance(contractsWithBurnerAllowances_[i], BURNER) != INFINITE_ALLOWANCE) { revert IncorrectBurnerAllowance(contractsWithBurnerAllowances_[i], BURNER); } } // NO and SimpleDVT new Burner allowances are to be zero the same as old Burner on pre upgrade state if (ILidoWithFinalizeUpgrade(LIDO).allowance(NODE_OPERATORS_REGISTRY, BURNER) != 0) { revert IncorrectBurnerAllowance(NODE_OPERATORS_REGISTRY, BURNER); } if (ILidoWithFinalizeUpgrade(LIDO).allowance(SIMPLE_DVT, BURNER) != 0) { revert IncorrectBurnerAllowance(SIMPLE_DVT, BURNER); } } function _assertProxyAdmin(IOssifiableProxy _proxy, address _admin) internal view { if (_proxy.proxy__getAdmin() != _admin) revert IncorrectProxyAdmin(address(_proxy)); } function _assertProxyImplementation(IOssifiableProxy _proxy, address _implementation) internal view { address actualImplementation = _proxy.proxy__getImplementation(); if (actualImplementation != _implementation) { revert IncorrectProxyImplementation(address(_proxy), actualImplementation); } } function _assertZeroOZRoleHolders(address _accessControlled, bytes32 _role) internal view { IAccessControlEnumerable accessControlled = IAccessControlEnumerable(_accessControlled); if (accessControlled.getRoleMemberCount(_role) != 0) { revert NonZeroRoleHolders(address(accessControlled), _role); } } function _assertSingleOZRoleHolder( address _accessControlled, bytes32 _role, address _holder ) internal view { IAccessControlEnumerable accessControlled = IAccessControlEnumerable(_accessControlled); if (accessControlled.getRoleMemberCount(_role) != 1 || accessControlled.getRoleMember(_role, 0) != _holder ) { revert IncorrectOZAccessControlRoleHolders(address(accessControlled), _role); } } function _assertTwoOZRoleHolders( address _accessControlled, bytes32 _role, address _holder1, address _holder2 ) internal view { address[] memory holders = new address[](2); holders[0] = _holder1; holders[1] = _holder2; _assertOZRoleHolders(_accessControlled, _role, holders); } function _assertOZRoleHolders( address _accessControlled, bytes32 _role, address[] memory _holders ) internal view { IAccessControlEnumerable accessControlled = IAccessControlEnumerable(_accessControlled); if (accessControlled.getRoleMemberCount(_role) != _holders.length) { revert IncorrectOZAccessControlRoleHolders(address(accessControlled), _role); } for (uint256 i = 0; i < _holders.length; i++) { if (accessControlled.getRoleMember(_role, i) != _holders[i]) { revert IncorrectOZAccessControlRoleHolders(address(accessControlled), _role); } } } function _assertAragonKernelImplementation(IAragonKernel _kernel, address _implementation) internal view { if (_kernel.getApp(_kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) != _implementation) { revert IncorrectAragonKernelImplementation(address(_kernel), _implementation); } } function _assertContractVersion(IVersioned _versioned, uint256 _expectedVersion) internal view { if (_versioned.getContractVersion() != _expectedVersion) { revert InvalidContractVersion(address(_versioned), _expectedVersion); } } function _isStartCalledInThisTx() internal view returns (bool isStartCalledInThisTx) { assembly { isStartCalledInThisTx := tload(UPGRADE_STARTED_SLOT) } } error OnlyAgentCanUpgrade(); error UpgradeAlreadyStarted(); error UpgradeAlreadyFinished(); error IncorrectProxyAdmin(address proxy); error IncorrectProxyImplementation(address proxy, address implementation); error InvalidContractVersion(address contractAddress, uint256 actualVersion); error IncorrectOZAccessControlRoleHolders(address contractAddress, bytes32 role); error NonZeroRoleHolders(address contractAddress, bytes32 role); error IncorrectAragonKernelImplementation(address kernel, address implementation); error StartAndFinishMustBeInSameTx(); error StartAlreadyCalledInThisTx(); error Expired(); error IncorrectBurnerSharesMigration(string reason); error IncorrectBurnerAllowance(address contractAddress, address burner); error BurnerMigrationNotAllowed(); error IncorrectVaultFactoryBeacon(address factory, address beacon); error IncorrectVaultFactoryDashboardImplementation(address factory, address delegation); error IncorrectUpgradeableBeaconOwner(address beacon, address owner); error IncorrectUpgradeableBeaconImplementation(address beacon, address implementation); error TotalSharesOrPooledEtherChanged(); error UnexpectedEasyTrackFactories(); error IncorrectTokenRateNotifierOwnerMigration(address notifier, address owner); error IncorrectTokenRateNotifierObserversLengthMigration(); error IncorrectTokenRateNotifierObserversMigration(); }
{
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"evmVersion": "cancun",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"components":[{"internalType":"address","name":"upgradeTemplate","type":"address"},{"internalType":"address","name":"timeConstraints","type":"address"},{"internalType":"uint256","name":"odcSlashingReserveWeRightShiftEpochs","type":"uint256"},{"internalType":"uint256","name":"odcSlashingReserveWeLeftShiftEpochs","type":"uint256"}],"internalType":"struct V3VoteScript.ScriptParams","name":"_params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DG_ITEMS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ENABLED_DAY_SPAN_END","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ENABLED_DAY_SPAN_START","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TEMPLATE","outputs":[{"internalType":"contract V3Template","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING_ITEMS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"proposalMetadata","type":"string"}],"name":"getEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"description","type":"string"},{"internalType":"string","name":"proposalMetadata","type":"string"}],"name":"getNewVoteCallBytecode","outputs":[{"internalType":"bytes","name":"newVoteBytecode","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVoteItems","outputs":[{"components":[{"internalType":"string","name":"description","type":"string"},{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct OmnibusBase.ScriptCall","name":"call","type":"tuple"}],"internalType":"struct OmnibusBase.VoteItem[]","name":"voteItems","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVotingVoteItems","outputs":[{"components":[{"internalType":"string","name":"description","type":"string"},{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct OmnibusBase.ScriptCall","name":"call","type":"tuple"}],"internalType":"struct OmnibusBase.VoteItem[]","name":"votingVoteItems","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"voteId","type":"uint256"},{"internalType":"string","name":"proposalMetadata","type":"string"}],"name":"isValidVoteScript","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"params","outputs":[{"internalType":"address","name":"upgradeTemplate","type":"address"},{"internalType":"address","name":"timeConstraints","type":"address"},{"internalType":"uint256","name":"odcSlashingReserveWeRightShiftEpochs","type":"uint256"},{"internalType":"uint256","name":"odcSlashingReserveWeLeftShiftEpochs","type":"uint256"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60e06040908082523461032d57608081614ba380380380916100218285610395565b83398101031261032d57815190608082016001600160401b0381118382101761038157835261004f816103b8565b918281526020806100618185016103b8565b92818101938452606087860151958883019687520151956060820196875260018060a01b0393849160048a518094819363134f0e8d60e11b8352165afa8015610377575f90610342575b6004915083858451168a519384809263969ac4c960e01b82525afa9182156103385785945f936102f9575b505090838092166080521660a0528181511660c05251169160018060a01b031992835f5416175f5551169060015416176001555160025551600355516147d690816103cd82396080518181816101430152610323015260a051816144cd015260c051818181610386015281816104f7015281816105e4015281816106330152818161068201528181610771015281816107bf0152818161080e0152818161085c0152818161097f015281816109cc01528181610a1a01528181610a9b01528181610aea01528181610be301528181610c3101528181610c8001528181610cce01528181610df401528181610e7401528181610ec201528181610f100152818161101801528181611066015281816110b4015281816111c10152818161120f0152818161125d01528181611360015281816113ae015281816113fd0152818161151901528181611568015281816115b7015281816116ac0152818161172c0152818161177a015281816117c8015281816118c9015281816119170152818161196601528181611a6901528181611ae901528181611b3701528181611b8501528181611c9201528181611ce101528181611e3101528181611e8001528181611fc601528181612014015281816120630152818161216f0152818161300c015281816130530152818161309b015281816130e10152818161324c015281816133bb0152818161350501528181613660015281816137b0015281816138f10152613a350152f35b809295508193503d8311610331575b6103128183610395565b8101031261032d5783928361032781936103b8565b926100d6565b5f80fd5b503d610308565b89513d5f823e3d90fd5b508281813d8311610370575b6103588183610395565b8101031261032d5761036b6004916103b8565b6100ab565b503d61034e565b88513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b601f909101601f19168101906001600160401b0382119082101761038157604052565b51906001600160a01b038216820361032d5756fe6080806040526004361015610012575f80fd5b5f3560e01c9081630859791414613ea5575080633089a79014613e5b578063326e29d314613e4057806337cbedcb14612fb057806364288f20146103b55780638bfd917414610371578063a3d3949e1461025f578063bcdb3f1214610243578063c5009da6146100f6578063cff0ab96146100b65763fbd784f914610095575f80fd5b346100b2575f3660031901126100b2576020604051620143708152f35b5f80fd5b346100b2575f3660031901126100b257608060018060a01b03805f5416906001541660025460035491604051938452602084015260408301526060820152f35b346100b25760403660031901126100b2576001600160401b036024358181116100b257610127903690600401613f81565b6040516305a55c1f60e41b815260048035908201525f816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa928315610238575f9361019d575b60208361018e8683815191012091614377565b82815191012014604051908152f35b9092503d805f853e6101af8185613f45565b8301610160848203126100b2576101c584614667565b506101d260208501614667565b506101df60408501614674565b506101ec60608501614674565b506101f960808501614674565b5061020660a08501614674565b506101208401519182116100b2576102256101409160039386016140e5565b93015110156100b25761018e602061017b565b6040513d5f823e3d90fd5b346100b2575f3660031901126100b257602060405161c4e08152f35b346100b25760403660031901126100b2576001600160401b036004358181116100b257610290903690600401613f81565b906024359081116100b2576102e661031261036d936102f86102c16102bc610358963690600401613f81565b614377565b60405163f4b0051360e01b6020820152608060248201529485939160a4850190613fc7565b83810360231901604485015290613fc7565b5f60648301525f608483015203601f198101835282613f45565b606060405161032081613ef4565b527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610353614688565b6146b7565b51604051918291602083526020830190613fc7565b0390f35b346100b2575f3660031901126100b2576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100b2575f3660031901126100b25760405161026081018181106001600160401b03821117612f9c57604052601281525f5b6102408110612f71575060018060a01b0360015416604051630f4ae8af60e31b602082015261c4e060248201526201437060448201526044815261042b81613f0f565b6040519161043883613ebe565b825260208201526040519061044c82613ebe565b60405161045881613f0f565b605581527f312e312e20456e737572652044472070726f706f73616c20657865637574696f60208201527f6e2069732077697468696e206461696c792074696d652077696e646f77202831604082015274343a303020555443202d2032333a3030205554432960581b6060820152825260208201526104d682614227565b526104e081614227565b50604051634f86473d60e01b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612f35575b505f5460405163111ce24560e11b6020820152600481526001600160a01b03909116926105679190849061056283613ebe565b61472f565b6040519061057482613ebe565b60405161058081613ed9565b602181527f312e322e2043616c6c20563354656d706c6174652e73746172745570677261646020820152606560f81b6040820152825260208201526105c482614248565b526105ce81614248565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612efb575b506040516357a7828960e01b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612ebf575b50604051633945993760e01b8152916020836004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612e82575b604051633ebdd0eb60e01b60208201526001600160a01b0390931660248085019190915283526106ee935061056283613ed9565b604051906106fb82613ebe565b60405161070781613ed9565b602781527f312e332e2055706772616465204c69646f4c6f6361746f7220696d706c656d65602082015266373a30ba34b7b760c91b60408201528252602082015261075182614258565b5261075b81614258565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612e48575b50604051633d7a9a9960e11b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612e0e575b50604051634f86473d60e01b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612dd2575b5060405162df194560e21b8152926020846004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa8015610238575f90612d97575b604051630a8ed3db60e01b60208201526001600160a01b039485166024820152931660448401527fb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd060648085019190915283526108f4935061056283613f2a565b6040519061090182613ebe565b60405161090d81613ed9565b602f81527f312e342e204772616e7420417261676f6e204150505f4d414e414745525f524f60208201526e1311481d1bc81d1a19481051d15395608a1b60408201528252602082015261095f82614268565b5261096981614268565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612d5d575b5060405162df194560e21b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612d23575b5060405162df194560e21b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612ce4575b506040516336e2987560e21b815291602090839060049082906001600160a01b03165afa918215610238575f92612cb0575b50604051634c0b4cd760e11b8152926020846004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa938415610238575f94612c7c575b50604051635ef608e160e11b8152936020856004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612c3f575b6040516302b96c9560e61b6020820152602481019590955260448501526001600160a01b03166064808501919091528352610b61935061056283613f2a565b60405190610b6e82613ebe565b604051610b7a81613ed9565b602681527f312e352e20536574204c69646f20696d706c656d656e746174696f6e20696e2060208201526512d95c9b995b60d21b604082015282526020820152610bc382614278565b52610bcd81614278565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612c05575b50604051633d7a9a9960e11b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612bcb575b50604051634f86473d60e01b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612b8f575b5060405162df194560e21b8152926020846004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa8015610238575f90612b54575b604051639d0effdb60e01b60208201526001600160a01b039485166024820152931660448401527fb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd06064808501919091528352610d66935061056283613f2a565b60405190610d7382613ebe565b604051610d7f81613ed9565b603281527f312e362e205265766f6b6520417261676f6e204150505f4d414e414745525f5260208201527113d31148199c9bdb481d1a19481051d1539560721b604082015282526020820152610dd482614288565b52610dde81614288565b506040516398f94a9360e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612b15575b5060405163337c70e360e21b815290602090829060049082906001600160a01b03165afa908115610238575f91612ae3575b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612aa9575b506040516398f94a9360e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612a6f575b506040516308b21f1760e41b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612a26575b5060405163d547741f60e01b6020820152602481018590526001600160a01b039091166044820152610f8e9290919061056283606481015b03601f198101855284613f45565b60405190610f9b82613ebe565b604051610fa781613ed9565b602e81527f312e372e205265766f6b6520524551554553545f4255524e5f5348415245535f60208201526d524f4c452066726f6d204c69646f60901b604082015282526020820152610ff883614298565b5261100282614298565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916129ec575b506040516398f94a9360e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916129b2575b50604051630960b9ff60e21b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612969575b5060405163d547741f60e01b6020820152602481018590526001600160a01b039091166044820152611128929091906105628360648101610f80565b6040519061113582613ebe565b60405161114181613ed9565b604081527f312e382e205265766f6b6520524551554553545f4255524e5f5348415245535f60208201527f524f4c452066726f6d2043757261746564207374616b696e67206d6f64756c656040820152825260208201526111a1836142a8565b526111ab826142a8565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f9161292f575b506040516398f94a9360e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916128f5575b50604051631607f91b60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916128ac575b5060405163d547741f60e01b6020820152602481018590526001600160a01b0390911660448201526112d1929091906105628360648101610f80565b604051906112de82613ebe565b6040516112ea81613ed9565b603381527f312e392e205265766f6b6520524551554553545f4255524e5f5348415245535f6020820152721493d31148199c9bdb4814da5b5c1b19511595606a1b604082015282526020820152611340836142b9565b5261134a826142b9565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612872575b506040516398f94a9360e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612838575b50604051635e51a9e360e11b8152916020836004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa928315610238575f936127f5575b5060405163d547741f60e01b602082015260248101949094526001600160a01b03909216604484015261147092916105628360648101610f80565b6040519061147d82613ebe565b60405161148981613f0f565b604781527f312e31302e205265766f6b6520524551554553545f4255524e5f53484152455360208201527f5f524f4c452066726f6d20436f6d6d756e697479205374616b696e67204163636040820152666f756e74696e6760c81b6060820152825260208201526114f9826142ca565b52611503816142ca565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916127bb575b506040516375e3ddbb60e01b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f9261277f575b50604051630e7e926f60e01b8152916020836004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f92612742575b604051633ebdd0eb60e01b60208201526001600160a01b039093166024808501919091528352611623935061056283613ed9565b6040519061163082613ebe565b60405161163c81613ed9565b602d81527f312e31312e2055706772616465204163636f756e74696e674f7261636c65206960208201526c36b83632b6b2b73a30ba34b7b760991b60408201528252602082015261168c826142db565b52611696816142db565b50604051636452fc7360e11b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612703575b50604051630746e74f60e21b815290602090829060049082906001600160a01b03165afa908115610238575f916126d1575b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612697575b50604051636452fc7360e11b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f9161265d575b506040516308b21f1760e41b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612614575b5060405163d547741f60e01b6020820152602481018590526001600160a01b03909116604482015261183c929091906105628360648101610f80565b6040519061184982613ebe565b60405161185581613ed9565b603181527f312e31322e205265766f6b65205245504f52545f524557415244535f4d494e5460208201527045445f524f4c452066726f6d204c69646f60781b6040820152825260208201526118a9836142ec565b526118b3826142ec565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916125da575b50604051636452fc7360e11b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916125a0575b50604051636dc3f2bd60e01b8152916020836004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa928315610238575f9361255d575b50604051632f2ff15d60e01b602082015260248101949094526001600160a01b0390921660448401526119d992916105628360648101610f80565b604051906119e682613ebe565b6040516119f281613ed9565b603481527f312e31332e204772616e74205245504f52545f524557415244535f4d494e5445602082015273445f524f4c4520746f204163636f756e74696e6760601b604082015282526020820152611a49826142fd565b52611a53816142fd565b506040516306243d3f60e31b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f9161251e575b5060405163a0c7f79960e01b815290602090829060049082906001600160a01b03165afa908115610238575f916124ec575b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916124b2575b506040516306243d3f60e31b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612478575b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f9161242f575b50604051632f2ff15d60e01b6020820152602481018590526001600160a01b039091166044820152611bf9929091906105628360648101610f80565b60405190611c0682613ebe565b604051611c1281613ed9565b603d81527f312e31342e204772616e74204f7261636c654461656d6f6e436f6e666967277360208201527f20434f4e4649475f4d414e414745525f524f4c4520746f204167656e74000000604082015282526020820152611c728361430e565b52611c7c8261430e565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916123f5575b506040516306243d3f60e31b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f926123b6575b50611d97906002549260405193602085015260208452611d3884613ebe565b6040519161056283610f80631594e07d60e11b9788602084015260406024840152601f60648401527f534c415348494e475f524553455256455f57455f52494748545f53484946540060848401526080604484015260a4830190613fc7565b60405190611da482613ebe565b604051611db081613ed9565b603f81527f312e31352e2053657420534c415348494e475f524553455256455f57455f524960208201527f4748545f5348494654206174204f7261636c654461656d6f6e436f6e66696700604082015282526020820152611e108461431f565b52611e1a8361431f565b50604051634f86473d60e01b8152906020826004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa918215610238575f9261237a575b506040516306243d3f60e31b8152916020836004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f9161233d575b611f2d93506105626003549360405194602086015260208552611eda85613ebe565b610f80604051958692602084015260406024840152601e60648401527f534c415348494e475f524553455256455f57455f4c4546545f5348494654000060848401526080604484015260a4830190613fc7565b60405190611f3a82613ebe565b604051611f4681613ed9565b603e81527f312e31362e2053657420534c415348494e475f524553455256455f57455f4c4560208201527f46545f5348494654206174204f7261636c654461656d6f6e436f6e6669670000604082015282526020820152611fa683614330565b52611fb082614330565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612303575b506040516306243d3f60e31b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f916122c9575b50604051634f86473d60e01b8152916020836004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa928315610238575f93612286575b5060405163d547741f60e01b602082015260248101949094526001600160a01b0390921660448401526120d692916105628360648101610f80565b604051906120e382613ebe565b6040516120ef81613ed9565b604081527f312e31372e205265766f6b65204f7261636c654461656d6f6e436f6e6669672760208201527f7320434f4e4649475f4d414e414745525f524f4c452066726f6d204167656e7460408201528252602082015261214f82614341565b5261215981614341565b50604051634f86473d60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115610238575f91612243575b5060405163340df28f60e01b60208201526004815261036d936121cd9261056283613ebe565b604051906121da82613ebe565b6040516121e681613ed9565b602381527f312e31382e2043616c6c20563354656d706c6174652e66696e6973685570677260208201526261646560e81b60408201528252602082015261222c82614352565b5261223681614352565b5060405191829182613feb565b90506020813d60201161227e575b8161225e60209383613f45565b810103126100b25761036d926122766121cd926140d1565b9150926121a7565b3d9150612251565b9392506020843d6020116122c1575b816122a260209383613f45565b810103126100b2576105626122b96120d6956140d1565b93945061209b565b3d9150612295565b90506020813d6020116122fb575b816122e460209383613f45565b810103126100b2576122f5906140d1565b8561204c565b3d91506122d7565b90506020813d602011612335575b8161231e60209383613f45565b810103126100b25761232f906140d1565b84611ffe565b3d9150612311565b90506020833d602011612372575b8161235860209383613f45565b810103126100b25761236c611f2d936140d1565b90611eb8565b3d915061234b565b9091506020813d6020116123ae575b8161239660209383613f45565b810103126100b2576123a7906140d1565b9085611e69565b3d9150612389565b9091506020813d6020116123ed575b816123d260209383613f45565b810103126100b2576123e6611d97916140d1565b9190611d19565b3d91506123c5565b90506020813d602011612427575b8161241060209383613f45565b810103126100b257612421906140d1565b84611cca565b3d9150612403565b9190506020823d602011612470575b8161244b60209383613f45565b810103126100b257611bf992610562612466610f80946140d1565b9293505092611bbd565b3d915061243e565b90506020813d6020116124aa575b8161249360209383613f45565b810103126100b2576124a4906140d1565b85611b6f565b3d9150612486565b90506020813d6020116124e4575b816124cd60209383613f45565b810103126100b2576124de906140d1565b84611b21565b3d91506124c0565b90506020813d602011612516575b8161250760209383613f45565b810103126100b2575183611ad3565b3d91506124fa565b90506020813d602011612555575b8161253960209383613f45565b810103126100b257602061254e6004926140d1565b9150611aa1565b3d915061252c565b9392506020843d602011612598575b8161257960209383613f45565b810103126100b2576105626125906119d9956140d1565b93945061199e565b3d915061256c565b90506020813d6020116125d2575b816125bb60209383613f45565b810103126100b2576125cc906140d1565b8561194f565b3d91506125ae565b90506020813d60201161260c575b816125f560209383613f45565b810103126100b257612606906140d1565b84611901565b3d91506125e8565b9190506020823d602011612655575b8161263060209383613f45565b810103126100b25761183c9261056261264b610f80946140d1565b9293505092611800565b3d9150612623565b90506020813d60201161268f575b8161267860209383613f45565b810103126100b257612689906140d1565b856117b2565b3d915061266b565b90506020813d6020116126c9575b816126b260209383613f45565b810103126100b2576126c3906140d1565b84611764565b3d91506126a5565b90506020813d6020116126fb575b816126ec60209383613f45565b810103126100b2575183611716565b3d91506126df565b90506020813d60201161273a575b8161271e60209383613f45565b810103126100b25760206127336004926140d1565b91506116e4565b3d9150612711565b91506020833d602011612777575b8161275d60209383613f45565b810103126100b257612771611623936140d1565b916115ef565b3d9150612750565b9091506020813d6020116127b3575b8161279b60209383613f45565b810103126100b2576127ac906140d1565b90846115a0565b3d915061278e565b90506020813d6020116127ed575b816127d660209383613f45565b810103126100b2576127e7906140d1565b83611551565b3d91506127c9565b9392506020843d602011612830575b8161281160209383613f45565b810103126100b257610562612828611470956140d1565b939450611435565b3d9150612804565b90506020813d60201161286a575b8161285360209383613f45565b810103126100b257612864906140d1565b856113e6565b3d9150612846565b90506020813d6020116128a4575b8161288d60209383613f45565b810103126100b25761289e906140d1565b84611398565b3d9150612880565b9190506020823d6020116128ed575b816128c860209383613f45565b810103126100b2576112d1926105626128e3610f80946140d1565b9293505092611295565b3d91506128bb565b90506020813d602011612927575b8161291060209383613f45565b810103126100b257612921906140d1565b85611247565b3d9150612903565b90506020813d602011612961575b8161294a60209383613f45565b810103126100b25761295b906140d1565b846111f9565b3d915061293d565b9190506020823d6020116129aa575b8161298560209383613f45565b810103126100b257611128926105626129a0610f80946140d1565b92935050926110ec565b3d9150612978565b90506020813d6020116129e4575b816129cd60209383613f45565b810103126100b2576129de906140d1565b8561109e565b3d91506129c0565b90506020813d602011612a1e575b81612a0760209383613f45565b810103126100b257612a18906140d1565b84611050565b3d91506129fa565b9190506020823d602011612a67575b81612a4260209383613f45565b810103126100b257610f8e92610562612a5d610f80946140d1565b9293505092610f48565b3d9150612a35565b90506020813d602011612aa1575b81612a8a60209383613f45565b810103126100b257612a9b906140d1565b85610efa565b3d9150612a7d565b90506020813d602011612adb575b81612ac460209383613f45565b810103126100b257612ad5906140d1565b84610eac565b3d9150612ab7565b90506020813d602011612b0d575b81612afe60209383613f45565b810103126100b2575183610e5e565b3d9150612af1565b90506020813d602011612b4c575b81612b3060209383613f45565b810103126100b2576020612b456004926140d1565b9150610e2c565b3d9150612b23565b506020843d602011612b87575b81612b6e60209383613f45565b810103126100b257612b82610d66946140d1565b610d05565b3d9150612b61565b9091506020813d602011612bc3575b81612bab60209383613f45565b810103126100b257612bbc906140d1565b9085610cb8565b3d9150612b9e565b90506020813d602011612bfd575b81612be660209383613f45565b810103126100b257612bf7906140d1565b84610c69565b3d9150612bd9565b90506020813d602011612c37575b81612c2060209383613f45565b810103126100b257612c31906140d1565b83610c1b565b3d9150612c13565b90506020853d602011612c74575b81612c5a60209383613f45565b810103126100b257612c6e610b61956140d1565b90610b22565b3d9150612c4d565b9093506020813d602011612ca8575b81612c9860209383613f45565b810103126100b257519286610ad3565b3d9150612c8b565b9091506020813d602011612cdc575b81612ccc60209383613f45565b810103126100b257519085610a84565b3d9150612cbf565b91506020823d602011612d1b575b81612cff60209383613f45565b810103126100b2576020612d146004936140d1565b9250610a52565b3d9150612cf2565b90506020813d602011612d55575b81612d3e60209383613f45565b810103126100b257612d4f906140d1565b84610a04565b3d9150612d31565b90506020813d602011612d8f575b81612d7860209383613f45565b810103126100b257612d89906140d1565b836109b7565b3d9150612d6b565b506020843d602011612dca575b81612db160209383613f45565b810103126100b257612dc56108f4946140d1565b610893565b3d9150612da4565b9091506020813d602011612e06575b81612dee60209383613f45565b810103126100b257612dff906140d1565b9085610846565b3d9150612de1565b90506020813d602011612e40575b81612e2960209383613f45565b810103126100b257612e3a906140d1565b846107f7565b3d9150612e1c565b90506020813d602011612e7a575b81612e6360209383613f45565b810103126100b257612e74906140d1565b836107a9565b3d9150612e56565b91506020833d602011612eb7575b81612e9d60209383613f45565b810103126100b257612eb16106ee936140d1565b916106ba565b3d9150612e90565b9091506020813d602011612ef3575b81612edb60209383613f45565b810103126100b257612eec906140d1565b908461066b565b3d9150612ece565b90506020813d602011612f2d575b81612f1660209383613f45565b810103126100b257612f27906140d1565b8361061c565b3d9150612f09565b9091506020813d602011612f69575b81612f5160209383613f45565b810103126100b257612f62906140d1565b908261052f565b3d9150612f44565b602090604051612f8081613ebe565b6060815282612f8d61462c565b818301528285010152016103e8565b634e487b7160e01b5f52604160045260245ffd5b346100b2575f3660031901126100b25760405161012081018181106001600160401b03821117612f9c57604052600881525f5b6101008110613e1557506040516311e1983560e11b81526001600160a01b0391906020816004817f000000000000000000000000000000000000000000000000000000000000000087165afa908115610238575f91613ddb575b506040516330d4631960e11b81526020816004817f000000000000000000000000000000000000000000000000000000000000000088165afa908115610238575f91613da1575b506040516322b97bb960e01b8152906020826004817f000000000000000000000000000000000000000000000000000000000000000089165afa918215610238575f92613d65575b5060405162ed2a8f60e31b81526020816004817f00000000000000000000000000000000000000000000000000000000000000008a165afa908115610238575f91613d2b575b50604051606083901b6001600160601b03191660208201526354544bcb60e01b603482015260188152906131769061314983613ebe565b6131686040519384926342f0498960e01b602085015260248401614645565b03601f198101835282613f45565b6040519061318382613ebe565b868516825260208201526040519061319a82613ebe565b6040516131a681613f0f565b605d81527f322e2041646420416c7465725469657273496e4f70657261746f72477269642060208201527f666163746f727920746f204561737920547261636b20287065726d697373696f60408201527f6e733a206f70657261746f72477269642c20616c74657254696572732900000060608201528252602082015261322c85614227565b5261323684614227565b5060405163aba1010b60e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000008a165afa908115610238575f91613cf1575b50604051606083901b6001600160601b0319166020820181905263e37a7c0b60e01b60348301526038820152632a95c8ed60e11b604c82015260308152906132c89061314983613ed9565b604051906132d582613ebe565b86851682526020820152604051906132ec82613ebe565b6040516132f881613f2a565b607481527f332e2041646420526567697374657247726f757073496e4f70657261746f724760208201527f72696420666163746f727920746f204561737920547261636b20287065726d6960408201527f7373696f6e733a206f70657261746f72477269642c20726567697374657247726060820152736f7570202b20726567697374657254696572732960601b60808201528252602082015261339b85614248565b526133a584614248565b5060405163f156d78960e01b81526020816004817f00000000000000000000000000000000000000000000000000000000000000008a165afa908115610238575f91613cb7575b50604051606083901b6001600160601b0319166020820152632a95c8ed60e11b603482015260188152906134239061314983613ebe565b6040519061343082613ebe565b868516825260208201526040519061344782613ebe565b60405161345381613f2a565b606381527f342e204164642052656769737465725469657273496e4f70657261746f72477260208201527f696420666163746f727920746f204561737920547261636b20287065726d697360408201527f73696f6e733a206f70657261746f72477269642c20726567697374657254696560608201526272732960e81b6080820152825260208201526134e585614258565b526134ef84614258565b50604051636be089cd60e11b81526020816004817f00000000000000000000000000000000000000000000000000000000000000008a165afa8015610238575f90613c7c575b60405160609390931b6001600160601b031916602084015263e52b608560e01b60348401526018835261356c915061314983613ebe565b6040519061357982613ebe565b858416825260208201526040519061359082613ebe565b60405161359c81613f2a565b607481527f352e204164642055706461746547726f75707353686172654c696d6974496e4f60208201527f70657261746f724772696420666163746f727920746f2045617379205472616360408201527f6b20287065726d697373696f6e733a206f70657261746f72477269642c2075706060820152736461746547726f757053686172654c696d69742960601b60808201528252602082015261363f84614268565b5261364983614268565b506040516316672a3760e21b8152906020826004817f000000000000000000000000000000000000000000000000000000000000000089165afa918215610238575f92613c40575b506001600160601b03199060601b16906136c860405191836020840152630a17d64760e21b60348401526018835261314983613ebe565b604051906136d582613ebe565b85841682526020820152604051906136ec82613ebe565b6040516136f881613f2a565b606981527f362e20416464205365744a61696c537461747573496e4f70657261746f72477260208201527f696420666163746f727920746f204561737920547261636b20287065726d697360408201527f73696f6e733a207661756c7473416461707465722c207365745661756c744a61606082015268696c5374617475732960b81b60808201528252602082015261379084614278565b5261379a83614278565b50604051633eb041a160e01b81526020816004817f000000000000000000000000000000000000000000000000000000000000000089165afa908115610238575f91613c06575b506138096040519183602084015263ed7139a760e01b60348401526018835261314983613ebe565b6040519061381682613ebe565b858416825260208201526040519061382d82613ebe565b60405161383981613f2a565b606981527f372e20416464205570646174655661756c747346656573496e4f70657261746f60208201527f724772696420666163746f727920746f204561737920547261636b202870657260408201527f6d697373696f6e733a207661756c7473416461707465722c207570646174655660608201526861756c74466565732960b81b6080820152825260208201526138d184614288565b526138db83614288565b506040516334090e4b60e11b81526020816004817f000000000000000000000000000000000000000000000000000000000000000089165afa908115610238575f91613bcc575b5061394a604051918360208401526319f58f8d60e11b60348401526018835261314983613ebe565b6040519061395782613ebe565b858416825260208201526040519061396e82613ebe565b60405161397a81613f2a565b606b81527f382e2041646420466f72636556616c696461746f724578697473496e5661756c60208201527f7448756220666163746f727920746f204561737920547261636b20287065726d60408201527f697373696f6e733a207661756c7473416461707465722c20666f72636556616c60608201526a696461746f72457869742960a81b608082015282526020820152613a1484614298565b52613a1e83614298565b50604051633c3c751160e21b8152936020856004817f000000000000000000000000000000000000000000000000000000000000000085165afa948515610238575f95613b8b575b50613abe61036d956040519360208501526325b1354560e21b603485015260188452613a9184613ebe565b613ab06040519485926342f0498960e01b602085015260248401614645565b03601f198101845283613f45565b60405192613acb84613ebe565b168252602082015260405190613ae082613ebe565b604051613aec81613f2a565b606681527f392e2041646420536f6369616c697a6542616444656274496e5661756c74487560208201527f6220666163746f727920746f204561737920547261636b20287065726d69737360408201527f696f6e733a207661756c7473416461707465722c20736f6369616c697a65426160608201526564446562742960d01b608082015282526020820152613b81826142a8565b52612236816142a8565b94506020853d602011613bc4575b81613ba660209383613f45565b810103126100b257613abe613bbd61036d966140d1565b9550613a66565b3d9150613b99565b90506020813d602011613bfe575b81613be760209383613f45565b810103126100b257613bf8906140d1565b85613922565b3d9150613bda565b90506020813d602011613c38575b81613c2160209383613f45565b810103126100b257613c32906140d1565b856137e1565b3d9150613c14565b9091506020813d602011613c74575b81613c5c60209383613f45565b810103126100b257613c6d906140d1565b9085613691565b3d9150613c4f565b506020813d602011613caf575b81613c9660209383613f45565b810103126100b257613caa61356c916140d1565b613535565b3d9150613c89565b90506020813d602011613ce9575b81613cd260209383613f45565b810103126100b257613ce3906140d1565b866133ec565b3d9150613cc5565b90506020813d602011613d23575b81613d0c60209383613f45565b810103126100b257613d1d906140d1565b8661327d565b3d9150613cff565b90506020813d602011613d5d575b81613d4660209383613f45565b810103126100b257613d57906140d1565b86613112565b3d9150613d39565b9091506020813d602011613d99575b81613d8160209383613f45565b810103126100b257613d92906140d1565b90856130cc565b3d9150613d74565b90506020813d602011613dd3575b81613dbc60209383613f45565b810103126100b257613dcd906140d1565b84613084565b3d9150613daf565b90506020813d602011613e0d575b81613df660209383613f45565b810103126100b257613e07906140d1565b8361303d565b3d9150613de9565b602090604051613e2481613ebe565b6060815282613e3161462c565b81830152828501015201612fe3565b346100b2575f3660031901126100b257602060405160088152f35b346100b25760203660031901126100b2576004356001600160401b0381116100b257613e916102bc61036d923690600401613f81565b604051918291602083526020830190613fc7565b346100b2575f3660031901126100b25780601260209252f35b604081019081106001600160401b03821117612f9c57604052565b606081019081106001600160401b03821117612f9c57604052565b602081019081106001600160401b03821117612f9c57604052565b608081019081106001600160401b03821117612f9c57604052565b60a081019081106001600160401b03821117612f9c57604052565b90601f801991011681019081106001600160401b03821117612f9c57604052565b6001600160401b038111612f9c57601f01601f191660200190565b81601f820112156100b257803590613f9882613f66565b92613fa66040519485613f45565b828452602083830101116100b257815f926020809301838601378301015290565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602080820190808352835180925260409283810182858560051b8401019601945f925b85841061401f575050505050505090565b909192939495968580614073600193603f1986820301885286838d51928161404e855185845285840190613fc7565b9401519082818603910152878060a01b03815116845201519181858201520190613fc7565b99019401940192959493919061400e565b6001600160401b038111612f9c5760051b60200190565b9291926140a782613f66565b916140b56040519384613f45565b8294818452818301116100b2578281602093845f96015e010152565b51906001600160a01b03821682036100b257565b9080601f830112156100b25781516140ff9260200161409b565b90565b9060209081838203126100b25782516001600160401b03938482116100b257019080601f830112156100b257815161413981614084565b94604061414881519788613f45565b828752858088019360051b860101948486116100b257868101935b86851061417557505050505050505090565b84518481116100b2578201601f19918483838a0301126100b25784519161419b83613ebe565b8a8101518781116100b257810189603f820112156100b2578981888e6141c4940151910161409b565b8352858101518781116100b25786910193848a0301126100b2578451916141ea83613ebe565b6141f58b85016140d1565b835285840151928784116100b2576142148a8d809796819701016140e5565b8482015283820152815201940193614163565b8051156142345760200190565b634e487b7160e01b5f52603260045260245ffd5b8051600110156142345760400190565b8051600210156142345760600190565b8051600310156142345760800190565b8051600410156142345760a00190565b8051600510156142345760c00190565b8051600610156142345760e00190565b805160071015614234576101000190565b805160081015614234576101200190565b805160091015614234576101400190565b8051600a1015614234576101600190565b8051600b1015614234576101800190565b8051600c1015614234576101a00190565b8051600d1015614234576101c00190565b8051600e1015614234576101e00190565b8051600f1015614234576102000190565b805160101015614234576102200190565b805160111015614234576102400190565b80518210156142345760209160051b010190565b60408051630321447960e51b815290925f82600481305afa918215614622575f92614606575b508151936143c26143ad86614084565b956143ba83519788613f45565b808752614084565b60209290601f19908101845f5b8281106145df575050505f5b8551811015614443576001906001600160a01b03866143fa838a614363565b5101515116868061440b848b614363565b510151015186519161441c83613ed9565b82525f8883015286820152614431828b614363565b5261443c818a614363565b50016143db565b50949095919350614452614688565b9460018060a01b03928551926353e51f8b60e01b8685015260648401876024860152825180915260848501908760848260051b8801019401915f905b898b8a85851061458e575050505050505091836144be6144ca936144f39695602319848303016044850152613fc7565b03908101835282613f45565b827f000000000000000000000000000000000000000000000000000000000000000016866146b7565b5082516337cbedcb60e01b81525f81600481305afa93841561458557505f93614561575b505f5b83518110156145595780614552838561453560019589614363565b51015151168580614546858a614363565b510151015190886146b7565b500161451a565b505050505190565b61457e9193503d805f833e6145768183613f45565b810190614102565b915f614517565b513d5f823e3d90fd5b82916145d1916001959697989a8d6083199082030188528b519183606093849281511684526001600160601b038882015116888501520151938201520190613fc7565b97019201920190929161448e565b85516145ea81613ed9565b5f81525f8382015260608782015282828c0101520185906143cf565b61461b9192503d805f833e6145768183613f45565b905f61439d565b84513d5f823e3d90fd5b6040519061463982613ebe565b60606020835f81520152565b6001600160a01b0390911681526040602082018190526140ff92910190613fc7565b519081151582036100b257565b51906001600160401b03821682036100b257565b6040519061469582613ef4565b60608252604051600160e01b602082015260048152826146b482613ebe565b52565b91603861472a916020809460606040516146d081613ef4565b5286519263ffffffff60e01b815160e01b166040519785899651918291018688015e8501926001600160601b03199060601b168484015260348301528051928391018583015e015f83820152036018810184520182613f45565b815290565b9161475b614780916131689361474361462c565b50606060405161475281613ef4565b52610353614688565b51604051928391631b291a8d60e31b6020840152602060248401526044830190613fc7565b6040519161478d83613ebe565b6001600160a01b0316825260208201529056fea2646970667358221220a5b6648ee8c5bcae1c8cbda0b190ea9ac631a4eb1ae1ee568781b9c080fedc0264736f6c6343000819003300000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85070000000000000000000000002a30f5ac03187674553024296bed35aa49749dda00000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000
Deployed Bytecode
0x6080806040526004361015610012575f80fd5b5f3560e01c9081630859791414613ea5575080633089a79014613e5b578063326e29d314613e4057806337cbedcb14612fb057806364288f20146103b55780638bfd917414610371578063a3d3949e1461025f578063bcdb3f1214610243578063c5009da6146100f6578063cff0ab96146100b65763fbd784f914610095575f80fd5b346100b2575f3660031901126100b2576020604051620143708152f35b5f80fd5b346100b2575f3660031901126100b257608060018060a01b03805f5416906001541660025460035491604051938452602084015260408301526060820152f35b346100b25760403660031901126100b2576001600160401b036024358181116100b257610127903690600401613f81565b6040516305a55c1f60e41b815260048035908201525f816024817f0000000000000000000000002e59a20f205bb85a89c53f1936454680651e618e6001600160a01b03165afa928315610238575f9361019d575b60208361018e8683815191012091614377565b82815191012014604051908152f35b9092503d805f853e6101af8185613f45565b8301610160848203126100b2576101c584614667565b506101d260208501614667565b506101df60408501614674565b506101ec60608501614674565b506101f960808501614674565b5061020660a08501614674565b506101208401519182116100b2576102256101409160039386016140e5565b93015110156100b25761018e602061017b565b6040513d5f823e3d90fd5b346100b2575f3660031901126100b257602060405161c4e08152f35b346100b25760403660031901126100b2576001600160401b036004358181116100b257610290903690600401613f81565b906024359081116100b2576102e661031261036d936102f86102c16102bc610358963690600401613f81565b614377565b60405163f4b0051360e01b6020820152608060248201529485939160a4850190613fc7565b83810360231901604485015290613fc7565b5f60648301525f608483015203601f198101835282613f45565b606060405161032081613ef4565b527f0000000000000000000000002e59a20f205bb85a89c53f1936454680651e618e6001600160a01b0316610353614688565b6146b7565b51604051918291602083526020830190613fc7565b0390f35b346100b2575f3660031901126100b2576040517f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03168152602090f35b346100b2575f3660031901126100b25760405161026081018181106001600160401b03821117612f9c57604052601281525f5b6102408110612f71575060018060a01b0360015416604051630f4ae8af60e31b602082015261c4e060248201526201437060448201526044815261042b81613f0f565b6040519161043883613ebe565b825260208201526040519061044c82613ebe565b60405161045881613f0f565b605581527f312e312e20456e737572652044472070726f706f73616c20657865637574696f60208201527f6e2069732077697468696e206461696c792074696d652077696e646f77202831604082015274343a303020555443202d2032333a3030205554432960581b6060820152825260208201526104d682614227565b526104e081614227565b50604051634f86473d60e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612f35575b505f5460405163111ce24560e11b6020820152600481526001600160a01b03909116926105679190849061056283613ebe565b61472f565b6040519061057482613ebe565b60405161058081613ed9565b602181527f312e322e2043616c6c20563354656d706c6174652e73746172745570677261646020820152606560f81b6040820152825260208201526105c482614248565b526105ce81614248565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612efb575b506040516357a7828960e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612ebf575b50604051633945993760e01b8152916020836004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612e82575b604051633ebdd0eb60e01b60208201526001600160a01b0390931660248085019190915283526106ee935061056283613ed9565b604051906106fb82613ebe565b60405161070781613ed9565b602781527f312e332e2055706772616465204c69646f4c6f6361746f7220696d706c656d65602082015266373a30ba34b7b760c91b60408201528252602082015261075182614258565b5261075b81614258565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612e48575b50604051633d7a9a9960e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612e0e575b50604051634f86473d60e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612dd2575b5060405162df194560e21b8152926020846004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa8015610238575f90612d97575b604051630a8ed3db60e01b60208201526001600160a01b039485166024820152931660448401527fb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd060648085019190915283526108f4935061056283613f2a565b6040519061090182613ebe565b60405161090d81613ed9565b602f81527f312e342e204772616e7420417261676f6e204150505f4d414e414745525f524f60208201526e1311481d1bc81d1a19481051d15395608a1b60408201528252602082015261095f82614268565b5261096981614268565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612d5d575b5060405162df194560e21b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612d23575b5060405162df194560e21b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612ce4575b506040516336e2987560e21b815291602090839060049082906001600160a01b03165afa918215610238575f92612cb0575b50604051634c0b4cd760e11b8152926020846004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa938415610238575f94612c7c575b50604051635ef608e160e11b8152936020856004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612c3f575b6040516302b96c9560e61b6020820152602481019590955260448501526001600160a01b03166064808501919091528352610b61935061056283613f2a565b60405190610b6e82613ebe565b604051610b7a81613ed9565b602681527f312e352e20536574204c69646f20696d706c656d656e746174696f6e20696e2060208201526512d95c9b995b60d21b604082015282526020820152610bc382614278565b52610bcd81614278565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612c05575b50604051633d7a9a9960e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612bcb575b50604051634f86473d60e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612b8f575b5060405162df194560e21b8152926020846004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa8015610238575f90612b54575b604051639d0effdb60e01b60208201526001600160a01b039485166024820152931660448401527fb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd06064808501919091528352610d66935061056283613f2a565b60405190610d7382613ebe565b604051610d7f81613ed9565b603281527f312e362e205265766f6b6520417261676f6e204150505f4d414e414745525f5260208201527113d31148199c9bdb481d1a19481051d1539560721b604082015282526020820152610dd482614288565b52610dde81614288565b506040516398f94a9360e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612b15575b5060405163337c70e360e21b815290602090829060049082906001600160a01b03165afa908115610238575f91612ae3575b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612aa9575b506040516398f94a9360e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612a6f575b506040516308b21f1760e41b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612a26575b5060405163d547741f60e01b6020820152602481018590526001600160a01b039091166044820152610f8e9290919061056283606481015b03601f198101855284613f45565b60405190610f9b82613ebe565b604051610fa781613ed9565b602e81527f312e372e205265766f6b6520524551554553545f4255524e5f5348415245535f60208201526d524f4c452066726f6d204c69646f60901b604082015282526020820152610ff883614298565b5261100282614298565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916129ec575b506040516398f94a9360e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916129b2575b50604051630960b9ff60e21b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612969575b5060405163d547741f60e01b6020820152602481018590526001600160a01b039091166044820152611128929091906105628360648101610f80565b6040519061113582613ebe565b60405161114181613ed9565b604081527f312e382e205265766f6b6520524551554553545f4255524e5f5348415245535f60208201527f524f4c452066726f6d2043757261746564207374616b696e67206d6f64756c656040820152825260208201526111a1836142a8565b526111ab826142a8565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f9161292f575b506040516398f94a9360e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916128f5575b50604051631607f91b60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916128ac575b5060405163d547741f60e01b6020820152602481018590526001600160a01b0390911660448201526112d1929091906105628360648101610f80565b604051906112de82613ebe565b6040516112ea81613ed9565b603381527f312e392e205265766f6b6520524551554553545f4255524e5f5348415245535f6020820152721493d31148199c9bdb4814da5b5c1b19511595606a1b604082015282526020820152611340836142b9565b5261134a826142b9565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612872575b506040516398f94a9360e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612838575b50604051635e51a9e360e11b8152916020836004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa928315610238575f936127f5575b5060405163d547741f60e01b602082015260248101949094526001600160a01b03909216604484015261147092916105628360648101610f80565b6040519061147d82613ebe565b60405161148981613f0f565b604781527f312e31302e205265766f6b6520524551554553545f4255524e5f53484152455360208201527f5f524f4c452066726f6d20436f6d6d756e697479205374616b696e67204163636040820152666f756e74696e6760c81b6060820152825260208201526114f9826142ca565b52611503816142ca565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916127bb575b506040516375e3ddbb60e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f9261277f575b50604051630e7e926f60e01b8152916020836004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f92612742575b604051633ebdd0eb60e01b60208201526001600160a01b039093166024808501919091528352611623935061056283613ed9565b6040519061163082613ebe565b60405161163c81613ed9565b602d81527f312e31312e2055706772616465204163636f756e74696e674f7261636c65206960208201526c36b83632b6b2b73a30ba34b7b760991b60408201528252602082015261168c826142db565b52611696816142db565b50604051636452fc7360e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612703575b50604051630746e74f60e21b815290602090829060049082906001600160a01b03165afa908115610238575f916126d1575b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612697575b50604051636452fc7360e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f9161265d575b506040516308b21f1760e41b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612614575b5060405163d547741f60e01b6020820152602481018590526001600160a01b03909116604482015261183c929091906105628360648101610f80565b6040519061184982613ebe565b60405161185581613ed9565b603181527f312e31322e205265766f6b65205245504f52545f524557415244535f4d494e5460208201527045445f524f4c452066726f6d204c69646f60781b6040820152825260208201526118a9836142ec565b526118b3826142ec565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916125da575b50604051636452fc7360e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916125a0575b50604051636dc3f2bd60e01b8152916020836004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa928315610238575f9361255d575b50604051632f2ff15d60e01b602082015260248101949094526001600160a01b0390921660448401526119d992916105628360648101610f80565b604051906119e682613ebe565b6040516119f281613ed9565b603481527f312e31332e204772616e74205245504f52545f524557415244535f4d494e5445602082015273445f524f4c4520746f204163636f756e74696e6760601b604082015282526020820152611a49826142fd565b52611a53816142fd565b506040516306243d3f60e31b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f9161251e575b5060405163a0c7f79960e01b815290602090829060049082906001600160a01b03165afa908115610238575f916124ec575b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916124b2575b506040516306243d3f60e31b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612478575b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f9161242f575b50604051632f2ff15d60e01b6020820152602481018590526001600160a01b039091166044820152611bf9929091906105628360648101610f80565b60405190611c0682613ebe565b604051611c1281613ed9565b603d81527f312e31342e204772616e74204f7261636c654461656d6f6e436f6e666967277360208201527f20434f4e4649475f4d414e414745525f524f4c4520746f204167656e74000000604082015282526020820152611c728361430e565b52611c7c8261430e565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916123f5575b506040516306243d3f60e31b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f926123b6575b50611d97906002549260405193602085015260208452611d3884613ebe565b6040519161056283610f80631594e07d60e11b9788602084015260406024840152601f60648401527f534c415348494e475f524553455256455f57455f52494748545f53484946540060848401526080604484015260a4830190613fc7565b60405190611da482613ebe565b604051611db081613ed9565b603f81527f312e31352e2053657420534c415348494e475f524553455256455f57455f524960208201527f4748545f5348494654206174204f7261636c654461656d6f6e436f6e66696700604082015282526020820152611e108461431f565b52611e1a8361431f565b50604051634f86473d60e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa918215610238575f9261237a575b506040516306243d3f60e31b8152916020836004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f9161233d575b611f2d93506105626003549360405194602086015260208552611eda85613ebe565b610f80604051958692602084015260406024840152601e60648401527f534c415348494e475f524553455256455f57455f4c4546545f5348494654000060848401526080604484015260a4830190613fc7565b60405190611f3a82613ebe565b604051611f4681613ed9565b603e81527f312e31362e2053657420534c415348494e475f524553455256455f57455f4c4560208201527f46545f5348494654206174204f7261636c654461656d6f6e436f6e6669670000604082015282526020820152611fa683614330565b52611fb082614330565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612303575b506040516306243d3f60e31b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f916122c9575b50604051634f86473d60e01b8152916020836004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa928315610238575f93612286575b5060405163d547741f60e01b602082015260248101949094526001600160a01b0390921660448401526120d692916105628360648101610f80565b604051906120e382613ebe565b6040516120ef81613ed9565b604081527f312e31372e205265766f6b65204f7261636c654461656d6f6e436f6e6669672760208201527f7320434f4e4649475f4d414e414745525f524f4c452066726f6d204167656e7460408201528252602082015261214f82614341565b5261215981614341565b50604051634f86473d60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85076001600160a01b03165afa908115610238575f91612243575b5060405163340df28f60e01b60208201526004815261036d936121cd9261056283613ebe565b604051906121da82613ebe565b6040516121e681613ed9565b602381527f312e31382e2043616c6c20563354656d706c6174652e66696e6973685570677260208201526261646560e81b60408201528252602082015261222c82614352565b5261223681614352565b5060405191829182613feb565b90506020813d60201161227e575b8161225e60209383613f45565b810103126100b25761036d926122766121cd926140d1565b9150926121a7565b3d9150612251565b9392506020843d6020116122c1575b816122a260209383613f45565b810103126100b2576105626122b96120d6956140d1565b93945061209b565b3d9150612295565b90506020813d6020116122fb575b816122e460209383613f45565b810103126100b2576122f5906140d1565b8561204c565b3d91506122d7565b90506020813d602011612335575b8161231e60209383613f45565b810103126100b25761232f906140d1565b84611ffe565b3d9150612311565b90506020833d602011612372575b8161235860209383613f45565b810103126100b25761236c611f2d936140d1565b90611eb8565b3d915061234b565b9091506020813d6020116123ae575b8161239660209383613f45565b810103126100b2576123a7906140d1565b9085611e69565b3d9150612389565b9091506020813d6020116123ed575b816123d260209383613f45565b810103126100b2576123e6611d97916140d1565b9190611d19565b3d91506123c5565b90506020813d602011612427575b8161241060209383613f45565b810103126100b257612421906140d1565b84611cca565b3d9150612403565b9190506020823d602011612470575b8161244b60209383613f45565b810103126100b257611bf992610562612466610f80946140d1565b9293505092611bbd565b3d915061243e565b90506020813d6020116124aa575b8161249360209383613f45565b810103126100b2576124a4906140d1565b85611b6f565b3d9150612486565b90506020813d6020116124e4575b816124cd60209383613f45565b810103126100b2576124de906140d1565b84611b21565b3d91506124c0565b90506020813d602011612516575b8161250760209383613f45565b810103126100b2575183611ad3565b3d91506124fa565b90506020813d602011612555575b8161253960209383613f45565b810103126100b257602061254e6004926140d1565b9150611aa1565b3d915061252c565b9392506020843d602011612598575b8161257960209383613f45565b810103126100b2576105626125906119d9956140d1565b93945061199e565b3d915061256c565b90506020813d6020116125d2575b816125bb60209383613f45565b810103126100b2576125cc906140d1565b8561194f565b3d91506125ae565b90506020813d60201161260c575b816125f560209383613f45565b810103126100b257612606906140d1565b84611901565b3d91506125e8565b9190506020823d602011612655575b8161263060209383613f45565b810103126100b25761183c9261056261264b610f80946140d1565b9293505092611800565b3d9150612623565b90506020813d60201161268f575b8161267860209383613f45565b810103126100b257612689906140d1565b856117b2565b3d915061266b565b90506020813d6020116126c9575b816126b260209383613f45565b810103126100b2576126c3906140d1565b84611764565b3d91506126a5565b90506020813d6020116126fb575b816126ec60209383613f45565b810103126100b2575183611716565b3d91506126df565b90506020813d60201161273a575b8161271e60209383613f45565b810103126100b25760206127336004926140d1565b91506116e4565b3d9150612711565b91506020833d602011612777575b8161275d60209383613f45565b810103126100b257612771611623936140d1565b916115ef565b3d9150612750565b9091506020813d6020116127b3575b8161279b60209383613f45565b810103126100b2576127ac906140d1565b90846115a0565b3d915061278e565b90506020813d6020116127ed575b816127d660209383613f45565b810103126100b2576127e7906140d1565b83611551565b3d91506127c9565b9392506020843d602011612830575b8161281160209383613f45565b810103126100b257610562612828611470956140d1565b939450611435565b3d9150612804565b90506020813d60201161286a575b8161285360209383613f45565b810103126100b257612864906140d1565b856113e6565b3d9150612846565b90506020813d6020116128a4575b8161288d60209383613f45565b810103126100b25761289e906140d1565b84611398565b3d9150612880565b9190506020823d6020116128ed575b816128c860209383613f45565b810103126100b2576112d1926105626128e3610f80946140d1565b9293505092611295565b3d91506128bb565b90506020813d602011612927575b8161291060209383613f45565b810103126100b257612921906140d1565b85611247565b3d9150612903565b90506020813d602011612961575b8161294a60209383613f45565b810103126100b25761295b906140d1565b846111f9565b3d915061293d565b9190506020823d6020116129aa575b8161298560209383613f45565b810103126100b257611128926105626129a0610f80946140d1565b92935050926110ec565b3d9150612978565b90506020813d6020116129e4575b816129cd60209383613f45565b810103126100b2576129de906140d1565b8561109e565b3d91506129c0565b90506020813d602011612a1e575b81612a0760209383613f45565b810103126100b257612a18906140d1565b84611050565b3d91506129fa565b9190506020823d602011612a67575b81612a4260209383613f45565b810103126100b257610f8e92610562612a5d610f80946140d1565b9293505092610f48565b3d9150612a35565b90506020813d602011612aa1575b81612a8a60209383613f45565b810103126100b257612a9b906140d1565b85610efa565b3d9150612a7d565b90506020813d602011612adb575b81612ac460209383613f45565b810103126100b257612ad5906140d1565b84610eac565b3d9150612ab7565b90506020813d602011612b0d575b81612afe60209383613f45565b810103126100b2575183610e5e565b3d9150612af1565b90506020813d602011612b4c575b81612b3060209383613f45565b810103126100b2576020612b456004926140d1565b9150610e2c565b3d9150612b23565b506020843d602011612b87575b81612b6e60209383613f45565b810103126100b257612b82610d66946140d1565b610d05565b3d9150612b61565b9091506020813d602011612bc3575b81612bab60209383613f45565b810103126100b257612bbc906140d1565b9085610cb8565b3d9150612b9e565b90506020813d602011612bfd575b81612be660209383613f45565b810103126100b257612bf7906140d1565b84610c69565b3d9150612bd9565b90506020813d602011612c37575b81612c2060209383613f45565b810103126100b257612c31906140d1565b83610c1b565b3d9150612c13565b90506020853d602011612c74575b81612c5a60209383613f45565b810103126100b257612c6e610b61956140d1565b90610b22565b3d9150612c4d565b9093506020813d602011612ca8575b81612c9860209383613f45565b810103126100b257519286610ad3565b3d9150612c8b565b9091506020813d602011612cdc575b81612ccc60209383613f45565b810103126100b257519085610a84565b3d9150612cbf565b91506020823d602011612d1b575b81612cff60209383613f45565b810103126100b2576020612d146004936140d1565b9250610a52565b3d9150612cf2565b90506020813d602011612d55575b81612d3e60209383613f45565b810103126100b257612d4f906140d1565b84610a04565b3d9150612d31565b90506020813d602011612d8f575b81612d7860209383613f45565b810103126100b257612d89906140d1565b836109b7565b3d9150612d6b565b506020843d602011612dca575b81612db160209383613f45565b810103126100b257612dc56108f4946140d1565b610893565b3d9150612da4565b9091506020813d602011612e06575b81612dee60209383613f45565b810103126100b257612dff906140d1565b9085610846565b3d9150612de1565b90506020813d602011612e40575b81612e2960209383613f45565b810103126100b257612e3a906140d1565b846107f7565b3d9150612e1c565b90506020813d602011612e7a575b81612e6360209383613f45565b810103126100b257612e74906140d1565b836107a9565b3d9150612e56565b91506020833d602011612eb7575b81612e9d60209383613f45565b810103126100b257612eb16106ee936140d1565b916106ba565b3d9150612e90565b9091506020813d602011612ef3575b81612edb60209383613f45565b810103126100b257612eec906140d1565b908461066b565b3d9150612ece565b90506020813d602011612f2d575b81612f1660209383613f45565b810103126100b257612f27906140d1565b8361061c565b3d9150612f09565b9091506020813d602011612f69575b81612f5160209383613f45565b810103126100b257612f62906140d1565b908261052f565b3d9150612f44565b602090604051612f8081613ebe565b6060815282612f8d61462c565b818301528285010152016103e8565b634e487b7160e01b5f52604160045260245ffd5b346100b2575f3660031901126100b25760405161012081018181106001600160401b03821117612f9c57604052600881525f5b6101008110613e1557506040516311e1983560e11b81526001600160a01b0391906020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850787165afa908115610238575f91613ddb575b506040516330d4631960e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850788165afa908115610238575f91613da1575b506040516322b97bb960e01b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850789165afa918215610238575f92613d65575b5060405162ed2a8f60e31b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85078a165afa908115610238575f91613d2b575b50604051606083901b6001600160601b03191660208201526354544bcb60e01b603482015260188152906131769061314983613ebe565b6131686040519384926342f0498960e01b602085015260248401614645565b03601f198101835282613f45565b6040519061318382613ebe565b868516825260208201526040519061319a82613ebe565b6040516131a681613f0f565b605d81527f322e2041646420416c7465725469657273496e4f70657261746f72477269642060208201527f666163746f727920746f204561737920547261636b20287065726d697373696f60408201527f6e733a206f70657261746f72477269642c20616c74657254696572732900000060608201528252602082015261322c85614227565b5261323684614227565b5060405163aba1010b60e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85078a165afa908115610238575f91613cf1575b50604051606083901b6001600160601b0319166020820181905263e37a7c0b60e01b60348301526038820152632a95c8ed60e11b604c82015260308152906132c89061314983613ed9565b604051906132d582613ebe565b86851682526020820152604051906132ec82613ebe565b6040516132f881613f2a565b607481527f332e2041646420526567697374657247726f757073496e4f70657261746f724760208201527f72696420666163746f727920746f204561737920547261636b20287065726d6960408201527f7373696f6e733a206f70657261746f72477269642c20726567697374657247726060820152736f7570202b20726567697374657254696572732960601b60808201528252602082015261339b85614248565b526133a584614248565b5060405163f156d78960e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85078a165afa908115610238575f91613cb7575b50604051606083901b6001600160601b0319166020820152632a95c8ed60e11b603482015260188152906134239061314983613ebe565b6040519061343082613ebe565b868516825260208201526040519061344782613ebe565b60405161345381613f2a565b606381527f342e204164642052656769737465725469657273496e4f70657261746f72477260208201527f696420666163746f727920746f204561737920547261636b20287065726d697360408201527f73696f6e733a206f70657261746f72477269642c20726567697374657254696560608201526272732960e81b6080820152825260208201526134e585614258565b526134ef84614258565b50604051636be089cd60e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85078a165afa8015610238575f90613c7c575b60405160609390931b6001600160601b031916602084015263e52b608560e01b60348401526018835261356c915061314983613ebe565b6040519061357982613ebe565b858416825260208201526040519061359082613ebe565b60405161359c81613f2a565b607481527f352e204164642055706461746547726f75707353686172654c696d6974496e4f60208201527f70657261746f724772696420666163746f727920746f2045617379205472616360408201527f6b20287065726d697373696f6e733a206f70657261746f72477269642c2075706060820152736461746547726f757053686172654c696d69742960601b60808201528252602082015261363f84614268565b5261364983614268565b506040516316672a3760e21b8152906020826004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850789165afa918215610238575f92613c40575b506001600160601b03199060601b16906136c860405191836020840152630a17d64760e21b60348401526018835261314983613ebe565b604051906136d582613ebe565b85841682526020820152604051906136ec82613ebe565b6040516136f881613f2a565b606981527f362e20416464205365744a61696c537461747573496e4f70657261746f72477260208201527f696420666163746f727920746f204561737920547261636b20287065726d697360408201527f73696f6e733a207661756c7473416461707465722c207365745661756c744a61606082015268696c5374617475732960b81b60808201528252602082015261379084614278565b5261379a83614278565b50604051633eb041a160e01b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850789165afa908115610238575f91613c06575b506138096040519183602084015263ed7139a760e01b60348401526018835261314983613ebe565b6040519061381682613ebe565b858416825260208201526040519061382d82613ebe565b60405161383981613f2a565b606981527f372e20416464205570646174655661756c747346656573496e4f70657261746f60208201527f724772696420666163746f727920746f204561737920547261636b202870657260408201527f6d697373696f6e733a207661756c7473416461707465722c207570646174655660608201526861756c74466565732960b81b6080820152825260208201526138d184614288565b526138db83614288565b506040516334090e4b60e11b81526020816004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850789165afa908115610238575f91613bcc575b5061394a604051918360208401526319f58f8d60e11b60348401526018835261314983613ebe565b6040519061395782613ebe565b858416825260208201526040519061396e82613ebe565b60405161397a81613f2a565b606b81527f382e2041646420466f72636556616c696461746f724578697473496e5661756c60208201527f7448756220666163746f727920746f204561737920547261636b20287065726d60408201527f697373696f6e733a207661756c7473416461707465722c20666f72636556616c60608201526a696461746f72457869742960a81b608082015282526020820152613a1484614298565b52613a1e83614298565b50604051633c3c751160e21b8152936020856004817f00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb850785165afa948515610238575f95613b8b575b50613abe61036d956040519360208501526325b1354560e21b603485015260188452613a9184613ebe565b613ab06040519485926342f0498960e01b602085015260248401614645565b03601f198101845283613f45565b60405192613acb84613ebe565b168252602082015260405190613ae082613ebe565b604051613aec81613f2a565b606681527f392e2041646420536f6369616c697a6542616444656274496e5661756c74487560208201527f6220666163746f727920746f204561737920547261636b20287065726d69737360408201527f696f6e733a207661756c7473416461707465722c20736f6369616c697a65426160608201526564446562742960d01b608082015282526020820152613b81826142a8565b52612236816142a8565b94506020853d602011613bc4575b81613ba660209383613f45565b810103126100b257613abe613bbd61036d966140d1565b9550613a66565b3d9150613b99565b90506020813d602011613bfe575b81613be760209383613f45565b810103126100b257613bf8906140d1565b85613922565b3d9150613bda565b90506020813d602011613c38575b81613c2160209383613f45565b810103126100b257613c32906140d1565b856137e1565b3d9150613c14565b9091506020813d602011613c74575b81613c5c60209383613f45565b810103126100b257613c6d906140d1565b9085613691565b3d9150613c4f565b506020813d602011613caf575b81613c9660209383613f45565b810103126100b257613caa61356c916140d1565b613535565b3d9150613c89565b90506020813d602011613ce9575b81613cd260209383613f45565b810103126100b257613ce3906140d1565b866133ec565b3d9150613cc5565b90506020813d602011613d23575b81613d0c60209383613f45565b810103126100b257613d1d906140d1565b8661327d565b3d9150613cff565b90506020813d602011613d5d575b81613d4660209383613f45565b810103126100b257613d57906140d1565b86613112565b3d9150613d39565b9091506020813d602011613d99575b81613d8160209383613f45565b810103126100b257613d92906140d1565b90856130cc565b3d9150613d74565b90506020813d602011613dd3575b81613dbc60209383613f45565b810103126100b257613dcd906140d1565b84613084565b3d9150613daf565b90506020813d602011613e0d575b81613df660209383613f45565b810103126100b257613e07906140d1565b8361303d565b3d9150613de9565b602090604051613e2481613ebe565b6060815282613e3161462c565b81830152828501015201612fe3565b346100b2575f3660031901126100b257602060405160088152f35b346100b25760203660031901126100b2576004356001600160401b0381116100b257613e916102bc61036d923690600401613f81565b604051918291602083526020830190613fc7565b346100b2575f3660031901126100b25780601260209252f35b604081019081106001600160401b03821117612f9c57604052565b606081019081106001600160401b03821117612f9c57604052565b602081019081106001600160401b03821117612f9c57604052565b608081019081106001600160401b03821117612f9c57604052565b60a081019081106001600160401b03821117612f9c57604052565b90601f801991011681019081106001600160401b03821117612f9c57604052565b6001600160401b038111612f9c57601f01601f191660200190565b81601f820112156100b257803590613f9882613f66565b92613fa66040519485613f45565b828452602083830101116100b257815f926020809301838601378301015290565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602080820190808352835180925260409283810182858560051b8401019601945f925b85841061401f575050505050505090565b909192939495968580614073600193603f1986820301885286838d51928161404e855185845285840190613fc7565b9401519082818603910152878060a01b03815116845201519181858201520190613fc7565b99019401940192959493919061400e565b6001600160401b038111612f9c5760051b60200190565b9291926140a782613f66565b916140b56040519384613f45565b8294818452818301116100b2578281602093845f96015e010152565b51906001600160a01b03821682036100b257565b9080601f830112156100b25781516140ff9260200161409b565b90565b9060209081838203126100b25782516001600160401b03938482116100b257019080601f830112156100b257815161413981614084565b94604061414881519788613f45565b828752858088019360051b860101948486116100b257868101935b86851061417557505050505050505090565b84518481116100b2578201601f19918483838a0301126100b25784519161419b83613ebe565b8a8101518781116100b257810189603f820112156100b2578981888e6141c4940151910161409b565b8352858101518781116100b25786910193848a0301126100b2578451916141ea83613ebe565b6141f58b85016140d1565b835285840151928784116100b2576142148a8d809796819701016140e5565b8482015283820152815201940193614163565b8051156142345760200190565b634e487b7160e01b5f52603260045260245ffd5b8051600110156142345760400190565b8051600210156142345760600190565b8051600310156142345760800190565b8051600410156142345760a00190565b8051600510156142345760c00190565b8051600610156142345760e00190565b805160071015614234576101000190565b805160081015614234576101200190565b805160091015614234576101400190565b8051600a1015614234576101600190565b8051600b1015614234576101800190565b8051600c1015614234576101a00190565b8051600d1015614234576101c00190565b8051600e1015614234576101e00190565b8051600f1015614234576102000190565b805160101015614234576102200190565b805160111015614234576102400190565b80518210156142345760209160051b010190565b60408051630321447960e51b815290925f82600481305afa918215614622575f92614606575b508151936143c26143ad86614084565b956143ba83519788613f45565b808752614084565b60209290601f19908101845f5b8281106145df575050505f5b8551811015614443576001906001600160a01b03866143fa838a614363565b5101515116868061440b848b614363565b510151015186519161441c83613ed9565b82525f8883015286820152614431828b614363565b5261443c818a614363565b50016143db565b50949095919350614452614688565b9460018060a01b03928551926353e51f8b60e01b8685015260648401876024860152825180915260848501908760848260051b8801019401915f905b898b8a85851061458e575050505050505091836144be6144ca936144f39695602319848303016044850152613fc7565b03908101835282613f45565b827f000000000000000000000000c1db28b3301331277e307fdcff8de28242a4486e16866146b7565b5082516337cbedcb60e01b81525f81600481305afa93841561458557505f93614561575b505f5b83518110156145595780614552838561453560019589614363565b51015151168580614546858a614363565b510151015190886146b7565b500161451a565b505050505190565b61457e9193503d805f833e6145768183613f45565b810190614102565b915f614517565b513d5f823e3d90fd5b82916145d1916001959697989a8d6083199082030188528b519183606093849281511684526001600160601b038882015116888501520151938201520190613fc7565b97019201920190929161448e565b85516145ea81613ed9565b5f81525f8382015260608782015282828c0101520185906143cf565b61461b9192503d805f833e6145768183613f45565b905f61439d565b84513d5f823e3d90fd5b6040519061463982613ebe565b60606020835f81520152565b6001600160a01b0390911681526040602082018190526140ff92910190613fc7565b519081151582036100b257565b51906001600160401b03821682036100b257565b6040519061469582613ef4565b60608252604051600160e01b602082015260048152826146b482613ebe565b52565b91603861472a916020809460606040516146d081613ef4565b5286519263ffffffff60e01b815160e01b166040519785899651918291018688015e8501926001600160601b03199060601b168484015260348301528051928391018583015e015f83820152036018810184520182613f45565b815290565b9161475b614780916131689361474361462c565b50606060405161475281613ef4565b52610353614688565b51604051928391631b291a8d60e31b6020840152602060248401526044830190613fc7565b6040519161478d83613ebe565b6001600160a01b0316825260208201529056fea2646970667358221220a5b6648ee8c5bcae1c8cbda0b190ea9ac631a4eb1ae1ee568781b9c080fedc0264736f6c63430008190033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb85070000000000000000000000002a30f5ac03187674553024296bed35aa49749dda00000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000
-----Decoded View---------------
Arg [0] : _params (tuple):
Arg [1] : upgradeTemplate (address): 0x34E01ecFebd403370b0879C628f8A5319dDb8507
Arg [2] : timeConstraints (address): 0x2a30F5aC03187674553024296bed35Aa49749DDa
Arg [3] : odcSlashingReserveWeRightShiftEpochs (uint256): 8192
Arg [4] : odcSlashingReserveWeLeftShiftEpochs (uint256): 8192
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 00000000000000000000000034e01ecfebd403370b0879c628f8a5319ddb8507
Arg [1] : 0000000000000000000000002a30f5ac03187674553024296bed35aa49749dda
Arg [2] : 0000000000000000000000000000000000000000000000000000000000002000
Arg [3] : 0000000000000000000000000000000000000000000000000000000000002000
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.