Overview
ETH Balance
0.038573166 ETH
Eth Value
$129.40 (@ $3,354.58/ETH)More Info
Private Name Tags
ContractCreator
Loading...
Loading
Minimal Proxy Contract for 0x223ca2fe219df1aad87787a0de4b65f541f08542
Contract Name:
FeeRecipient
Compiler Version
v0.8.13+commit.abaa5c0e
Contract Source Code (Solidity Standard Json-Input format)
//SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.10; import "./interfaces/IFeeDispatcher.sol"; contract FeeRecipient { /// @notice Constructor replay prevention bool internal initialized; /// @notice Address where funds are sent to be dispatched IFeeDispatcher internal dispatcher; /// @notice Public Key root assigned to this receiver bytes32 internal publicKeyRoot; error AlreadyInitialized(); /// @notice Initializes the receiver /// @param _dispatcher Address that will handle the fee dispatching /// @param _publicKeyRoot Public Key root assigned to this receiver function init(address _dispatcher, bytes32 _publicKeyRoot) external { if (initialized) { revert AlreadyInitialized(); } initialized = true; dispatcher = IFeeDispatcher(_dispatcher); publicKeyRoot = _publicKeyRoot; } /// @notice Empty calldata fallback receive() external payable {} /// @notice Non-empty calldata fallback fallback() external payable {} /// @notice Triggers a withdrawal by sending its funds + its public key root to the dispatcher /// @dev Can be called by any wallet as recipients are not parameters function withdraw() external { dispatcher.dispatch{value: address(this).balance}(publicKeyRoot); } /// @notice Retrieve the assigned public key root function getPublicKeyRoot() external view returns (bytes32) { return publicKeyRoot; } /// @notice retrieve the assigned withdrawer function getWithdrawer() external view returns (address) { return dispatcher.getWithdrawer(publicKeyRoot); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.0; /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified * proxy whose upgrades are fully controlled by the current implementation. */ interface IERC1822Proxiable { /** * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation * address. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. */ function proxiableUUID() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol) pragma solidity ^0.8.0; /** * @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. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/Clones.sol) pragma solidity ^0.8.0; /** * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 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. * * _Available since v3.4._ */ library Clones { /** * @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) { assembly { let ptr := mload(0x40) mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) mstore(add(ptr, 0x14), shl(0x60, implementation)) mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) instance := create(0, ptr, 0x37) } require(instance != address(0), "ERC1167: create failed"); } /** * @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 time will revert, since * the clones cannot be deployed twice at the same address. */ function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { assembly { let ptr := mload(0x40) mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) mstore(add(ptr, 0x14), shl(0x60, implementation)) mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) instance := create2(0, ptr, 0x37, salt) } require(instance != address(0), "ERC1167: create2 failed"); } /** * @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 { let ptr := mload(0x40) mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) mstore(add(ptr, 0x14), shl(0x60, implementation)) mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000) mstore(add(ptr, 0x38), shl(0x60, deployer)) mstore(add(ptr, 0x4c), salt) mstore(add(ptr, 0x6c), keccak256(ptr, 0x37)) predicted := keccak256(add(ptr, 0x37), 0x55) } } /** * @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)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol) pragma solidity ^0.8.0; import "../Proxy.sol"; import "./ERC1967Upgrade.sol"; /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an * implementation address that can be changed. This address is stored in storage in the location specified by * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * implementation behind the proxy. */ contract ERC1967Proxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * function call, and allows initializating the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); _upgradeToAndCall(_logic, _data, false); } /** * @dev Returns the current implementation address. */ function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (proxy/ERC1967/ERC1967Upgrade.sol) pragma solidity ^0.8.2; import "../beacon/IBeacon.sol"; import "../../interfaces/draft-IERC1822.sol"; import "../../utils/Address.sol"; import "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ * * @custom:oz-upgrades-unsafe-allow delegatecall */ abstract contract ERC1967Upgrade { // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @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 EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall( address newImplementation, bytes memory data, bool forceCall ) internal { _upgradeTo(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallUUPS( address newImplementation, bytes memory data, bool forceCall ) internal { // Upgrades from old implementations will perform a rollback test. This test requires the new // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing // this special case will break upgrade paths from old UUPS implementation to new ones. if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) { _setImplementation(newImplementation); } else { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID"); } catch { revert("ERC1967Upgrade: new implementation is not UUPS"); } _upgradeToAndCall(newImplementation, data, forceCall); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Emitted when the beacon is upgraded. */ event BeaconUpgraded(address indexed beacon); /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlot.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract"); require( Address.isContract(IBeacon(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall( address newBeacon, bytes memory data, bool forceCall ) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (proxy/Proxy.sol) pragma solidity ^0.8.0; /** * @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 overriden 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 internall call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _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(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive() external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overriden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol) pragma solidity ^0.8.0; import "../ERC1967/ERC1967Proxy.sol"; /** * @dev This contract implements a proxy that is upgradeable by an admin. * * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * clashing], which can potentially be used in an attack, this contract uses the * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two * things that go hand in hand: * * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if * that call matches one of the admin functions exposed by the proxy itself. * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the * implementation. If the admin tries to call a function on the implementation it will fail with an error that says * "admin cannot fallback to proxy target". * * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due * to sudden errors when trying to call a function from the proxy implementation. * * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. */ contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ constructor( address _logic, address admin_, bytes memory _data ) payable ERC1967Proxy(_logic, _data) { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); _changeAdmin(admin_); } /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. */ modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } /** * @dev Returns the current admin. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ function admin() external ifAdmin returns (address admin_) { admin_ = _getAdmin(); } /** * @dev Returns the current implementation. * * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function implementation() external ifAdmin returns (address implementation_) { implementation_ = _implementation(); } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. * * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. */ function changeAdmin(address newAdmin) external virtual ifAdmin { _changeAdmin(newAdmin); } /** * @dev Upgrade the implementation of the proxy. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. */ function upgradeTo(address newImplementation) external ifAdmin { _upgradeToAndCall(newImplementation, bytes(""), false); } /** * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the * proxied contract. * * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. */ function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { _upgradeToAndCall(newImplementation, data, true); } /** * @dev Returns the current admin. */ function _admin() internal view virtual returns (address) { return _getAdmin(); } /** * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. */ function _beforeFallback() internal virtual override { require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); super._beforeFallback(); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { // 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 { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol) pragma solidity ^0.8.0; /** * @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 ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { assembly { r.slot := slot } } }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.10; import "./interfaces/IFeeDispatcher.sol"; import "./libs/DispatchersStorageLib.sol"; import "./interfaces/IFeeRecipient.sol"; contract AuthorizedFeeRecipient is IFeeRecipient { /// @notice Constructor replay prevention bool internal initialized; /// @notice Address where funds are sent to be dispatched IFeeDispatcher internal dispatcher; /// @notice Public Key root assigned to this receiver bytes32 internal publicKeyRoot; /// @notice Address of the staking contract address internal stakingContract; error AlreadyInitialized(); error Unauthorized(); /// @notice Initializes the receiver /// @param _dispatcher Address that will handle the fee dispatching /// @param _publicKeyRoot Public Key root assigned to this receiver function init(address _dispatcher, bytes32 _publicKeyRoot) external { if (initialized) { revert AlreadyInitialized(); } initialized = true; dispatcher = IFeeDispatcher(_dispatcher); publicKeyRoot = _publicKeyRoot; stakingContract = msg.sender; // The staking contract always calls init } /// @notice Empty calldata fallback receive() external payable {} /// @notice Non-empty calldata fallback fallback() external payable {} /// @notice Triggers a withdrawal by sending its funds + its public key root to the dispatcher /// @dev Can be called only be called through the staking contract function withdraw() external { if (msg.sender != stakingContract) { revert Unauthorized(); } dispatcher.dispatch{value: address(this).balance}(publicKeyRoot); } /// @notice Retrieve the assigned public key root function getPublicKeyRoot() external view returns (bytes32) { return publicKeyRoot; } /// @notice retrieve the assigned withdrawer function getWithdrawer() external view returns (address) { return dispatcher.getWithdrawer(publicKeyRoot); } }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.10; import "./libs/DispatchersStorageLib.sol"; import "./interfaces/IStakingContractFeeDetails.sol"; import "./interfaces/IFeeDispatcher.sol"; /// @title Consensus Layer Fee Recipient /// @author Kiln /// @notice This contract can be used to receive fees from a validator and split them with a node operator contract ConsensusLayerFeeDispatcher is IFeeDispatcher { using DispatchersStorageLib for bytes32; event Withdrawal( address indexed withdrawer, address indexed feeRecipient, bytes32 pubKeyRoot, uint256 rewards, uint256 nodeOperatorFee, uint256 treasuryFee ); error TreasuryReceiveError(bytes errorData); error FeeRecipientReceiveError(bytes errorData); error WithdrawerReceiveError(bytes errorData); error ZeroBalanceWithdrawal(); error AlreadyInitialized(); error InvalidCall(); bytes32 internal constant STAKING_CONTRACT_ADDRESS_SLOT = keccak256("ConsensusLayerFeeRecipient.stakingContractAddress"); uint256 internal constant BASIS_POINTS = 10_000; bytes32 internal constant VERSION_SLOT = keccak256("ConsensusLayerFeeRecipient.version"); /// @notice Ensures an initialisation call has been called only once per _version value /// @param _version The current initialisation value modifier init(uint256 _version) { if (_version != VERSION_SLOT.getUint256() + 1) { revert AlreadyInitialized(); } VERSION_SLOT.setUint256(_version); _; } /// @notice Constructor method allowing us to prevent calls to initCLFR by setting the appropriate version constructor(uint256 _version) { VERSION_SLOT.setUint256(_version); } /// @notice Initialize the contract by storing the staking contract /// @param _stakingContract Address of the Staking Contract function initCLD(address _stakingContract) external init(1) { STAKING_CONTRACT_ADDRESS_SLOT.setAddress(_stakingContract); } /// @notice Performs a withdrawal on this contract's balance function dispatch(bytes32 _publicKeyRoot) external payable { IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails( STAKING_CONTRACT_ADDRESS_SLOT.getAddress() ); uint256 balance = address(this).balance; // this has taken into account msg.value if (balance == 0) { revert ZeroBalanceWithdrawal(); } bool exitRequested = stakingContract.getExitRequestedFromRoot(_publicKeyRoot); bool withdrawn = stakingContract.getWithdrawnFromPublicKeyRoot(_publicKeyRoot); uint256 nonExemptBalance = balance; if (exitRequested && balance >= 31 ether && !withdrawn) { // If the skimmed rewards were withdrawn and the validator then underperformed // an healthy exit can be slightly lower than 32 ETH // We exempt the balance up to 32 ETH, happens only once. // !withdrawn prevents this logic being reused to not pay the fee on rewards uint256 exemption = nonExemptBalance > 32 ether ? 32 ether : nonExemptBalance; nonExemptBalance -= exemption; stakingContract.toggleWithdrawnFromPublicKeyRoot(_publicKeyRoot); } // In case of slashing the exit is not requested we don't exempt anything // This is in case of slashing, the staker will be rebated manually // A slashed validator may have accumulated enough skimmed rewards to still have a balance > 32 ETH // All of this will be taken into account and the staker will be compensated for the commission taken // on its principal and the loss according to the SLA described in the Terms&Conditions uint256 globalFee = (nonExemptBalance * stakingContract.getGlobalFee()) / BASIS_POINTS; uint256 operatorFee = (globalFee * stakingContract.getOperatorFee()) / BASIS_POINTS; address operator = stakingContract.getOperatorFeeRecipient(_publicKeyRoot); address treasury = stakingContract.getTreasury(); address withdrawer = stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot); (bool status, bytes memory data) = withdrawer.call{value: balance - globalFee}(""); if (status == false) { revert WithdrawerReceiveError(data); } if (globalFee > 0) { (status, data) = treasury.call{value: globalFee - operatorFee}(""); if (status == false) { revert TreasuryReceiveError(data); } } if (operatorFee > 0) { (status, data) = operator.call{value: operatorFee}(""); if (status == false) { revert FeeRecipientReceiveError(data); } } emit Withdrawal( withdrawer, operator, _publicKeyRoot, balance - globalFee, operatorFee, globalFee - operatorFee ); } /// @notice Retrieve the staking contract address function getStakingContract() external view returns (address) { return STAKING_CONTRACT_ADDRESS_SLOT.getAddress(); } /// @notice Retrieve the assigned withdrawer for the given public key root /// @param _publicKeyRoot Public key root to get the owner function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address) { IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails( STAKING_CONTRACT_ADDRESS_SLOT.getAddress() ); return stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot); } receive() external payable { revert InvalidCall(); } fallback() external payable { revert InvalidCall(); } }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.10; import "./libs/DispatchersStorageLib.sol"; import "./interfaces/IStakingContractFeeDetails.sol"; import "./interfaces/IFeeDispatcher.sol"; /// @title Execution Layer Fee Recipient /// @author Kiln /// @notice This contract can be used to receive fees from a validator and split them with a node operator contract ExecutionLayerFeeDispatcher is IFeeDispatcher { using DispatchersStorageLib for bytes32; event Withdrawal( address indexed withdrawer, address indexed feeRecipient, bytes32 pubKeyRoot, uint256 rewards, uint256 nodeOperatorFee, uint256 treasuryFee ); error TreasuryReceiveError(bytes errorData); error FeeRecipientReceiveError(bytes errorData); error WithdrawerReceiveError(bytes errorData); error ZeroBalanceWithdrawal(); error AlreadyInitialized(); error InvalidCall(); bytes32 internal constant STAKING_CONTRACT_ADDRESS_SLOT = keccak256("ExecutionLayerFeeRecipient.stakingContractAddress"); uint256 internal constant BASIS_POINTS = 10_000; bytes32 internal constant VERSION_SLOT = keccak256("ExecutionLayerFeeRecipient.version"); /// @notice Ensures an initialisation call has been called only once per _version value /// @param _version The current initialisation value modifier init(uint256 _version) { if (_version != VERSION_SLOT.getUint256() + 1) { revert AlreadyInitialized(); } VERSION_SLOT.setUint256(_version); _; } /// @notice Constructor method allowing us to prevent calls to initCLFR by setting the appropriate version constructor(uint256 _version) { VERSION_SLOT.setUint256(_version); } /// @notice Initialize the contract by storing the staking contract and the public key in storage /// @param _stakingContract Address of the Staking Contract function initELD(address _stakingContract) external init(1) { STAKING_CONTRACT_ADDRESS_SLOT.setAddress(_stakingContract); } /// @notice Performs a withdrawal on this contract's balance function dispatch(bytes32 _publicKeyRoot) external payable { uint256 balance = address(this).balance; if (balance == 0) { revert ZeroBalanceWithdrawal(); } IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails( STAKING_CONTRACT_ADDRESS_SLOT.getAddress() ); address withdrawer = stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot); address operator = stakingContract.getOperatorFeeRecipient(_publicKeyRoot); address treasury = stakingContract.getTreasury(); uint256 globalFee = (balance * stakingContract.getGlobalFee()) / BASIS_POINTS; uint256 operatorFee = (globalFee * stakingContract.getOperatorFee()) / BASIS_POINTS; (bool status, bytes memory data) = withdrawer.call{value: balance - globalFee}(""); if (status == false) { revert WithdrawerReceiveError(data); } if (globalFee > 0) { (status, data) = treasury.call{value: globalFee - operatorFee}(""); if (status == false) { revert TreasuryReceiveError(data); } } if (operatorFee > 0) { (status, data) = operator.call{value: operatorFee}(""); if (status == false) { revert FeeRecipientReceiveError(data); } } emit Withdrawal( withdrawer, operator, _publicKeyRoot, balance - globalFee, operatorFee, globalFee - operatorFee ); } /// @notice Retrieve the staking contract address function getStakingContract() external view returns (address) { return STAKING_CONTRACT_ADDRESS_SLOT.getAddress(); } /// @notice Retrieve the assigned withdrawer for the given public key root /// @param _publicKeyRoot Public key root to get the owner function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address) { IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails( STAKING_CONTRACT_ADDRESS_SLOT.getAddress() ); return stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot); } receive() external payable { revert InvalidCall(); } fallback() external payable { revert InvalidCall(); } }
// SPDX-License-Identifier: MIT pragma solidity >=0.8.10; interface IDepositContract { function deposit( bytes calldata pubkey, bytes calldata withdrawalCredentials, bytes calldata signature, bytes32 depositDataRoot ) external payable; }
// SPDX-License-Identifier: MIT pragma solidity >=0.8.10; interface IFeeDispatcher { function dispatch(bytes32 _publicKeyRoot) external payable; function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address); }
// SPDX-License-Identifier: MIT pragma solidity >=0.8.10; interface IFeeRecipient { function init(address _dispatcher, bytes32 _publicKeyRoot) external; function withdraw() external; }
// SPDX-License-Identifier: MIT pragma solidity >=0.8.10; interface IStakingContractFeeDetails { function getWithdrawerFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (address); function getTreasury() external view returns (address); function getOperatorFeeRecipient(bytes32 pubKeyRoot) external view returns (address); function getGlobalFee() external view returns (uint256); function getOperatorFee() external view returns (uint256); function getExitRequestedFromRoot(bytes32 _publicKeyRoot) external view returns (bool); function getWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (bool); function toggleWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external; }
//SPDX-License-Identifier: MIT pragma solidity >=0.8.10; /// Based on GNSPS/BytesLib.sol library BytesLib { function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // Store the length of the first bytes array at the beginning of // the memory for tempBytes. let length := mload(_preBytes) mstore(tempBytes, length) // Maintain a memory counter for the current write location in the // temp bytes array by adding the 32 bytes for the array length to // the starting location. let mc := add(tempBytes, 0x20) // Stop copying when the memory counter reaches the length of the // first bytes array. let end := add(mc, length) for { // Initialize a copy counter to the start of the _preBytes data, // 32 bytes into its memory. let cc := add(_preBytes, 0x20) } lt(mc, end) { // Increase both counters by 32 bytes each iteration. mc := add(mc, 0x20) cc := add(cc, 0x20) } { // Write the _preBytes data into the tempBytes memory 32 bytes // at a time. mstore(mc, mload(cc)) } // Add the length of _postBytes to the current length of tempBytes // and store it as the new length in the first 32 bytes of the // tempBytes memory. length := mload(_postBytes) mstore(tempBytes, add(length, mload(tempBytes))) // Move the memory counter back from a multiple of 0x20 to the // actual end of the _preBytes data. mc := end // Stop copying when the memory counter reaches the new combined // length of the arrays. end := add(mc, length) for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } // Update the free-memory pointer by padding our last write location // to 32 bytes: add 31 bytes to the end of tempBytes to move to the // next 32 byte block, then round down to the nearest multiple of // 32. If the sum of the length of the two arrays is zero then add // one before rounding down to leave a blank 32 bytes (the length block with 0). mstore( 0x40, and( add(add(end, iszero(add(length, mload(_preBytes)))), 31), not(31) // Round down to the nearest 32 bytes. ) ) } return tempBytes; } function slice( bytes memory _bytes, uint256 _start, uint256 _length ) internal pure returns (bytes memory) { require(_length + 31 >= _length, "slice_overflow"); require(_bytes.length >= _start + _length, "slice_outOfBounds"); bytes memory tempBytes; assembly { switch iszero(_length) case 0 { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // The first word of the slice result is potentially a partial // word read from the original array. To read it, we calculate // the length of that partial word and start copying that many // bytes into the array. The first word we copy will start with // data we don't care about, but the last `lengthmod` bytes will // land at the beginning of the contents of the new array. When // we're done copying, we overwrite the full first word with // the actual length of the slice. let lengthmod := and(_length, 31) // The multiplication in the next line is necessary // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) //update free-memory pointer //allocating the array padded to 32 bytes like the compiler does now mstore(0x40, and(add(mc, 31), not(31))) } //if we want a zero-length slice let's just return a zero-length array default { tempBytes := mload(0x40) //zero out the 32 bytes slice we are about to return //we need to do it because Solidity does not garbage collect mstore(tempBytes, 0) mstore(0x40, add(tempBytes, 0x20)) } } return tempBytes; } }
//SPDX-License-Identifier: MIT pragma solidity >=0.8.10; library DispatchersStorageLib { function getUint256(bytes32 position) internal view returns (uint256 data) { assembly { data := sload(position) } } function setUint256(bytes32 position, uint256 data) internal { assembly { sstore(position, data) } } function getAddress(bytes32 position) internal view returns (address data) { assembly { data := sload(position) } } function setAddress(bytes32 position, address data) internal { assembly { sstore(position, data) } } }
//SPDX-License-Identifier: MIT pragma solidity >=0.8.10; library StakingContractStorageLib { function getUint256(bytes32 position) internal view returns (uint256 data) { assembly { data := sload(position) } } function setUint256(bytes32 position, uint256 data) internal { assembly { sstore(position, data) } } function getAddress(bytes32 position) internal view returns (address data) { assembly { data := sload(position) } } function setAddress(bytes32 position, address data) internal { assembly { sstore(position, data) } } function getBool(bytes32 position) internal view returns (bool data) { assembly { data := sload(position) } } function setBool(bytes32 position, bool data) internal { assembly { sstore(position, data) } } /* ======================================== =========================================== =========================================*/ bytes32 internal constant VERSION_SLOT = keccak256("StakingContract.version"); function getVersion() internal view returns (uint256) { return getUint256(VERSION_SLOT); } function setVersion(uint256 _newVersion) internal { setUint256(VERSION_SLOT, _newVersion); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant ADMIN_SLOT = keccak256("StakingContract.admin"); bytes32 internal constant PENDING_ADMIN_SLOT = keccak256("StakingContract.pendingAdmin"); function getAdmin() internal view returns (address) { return getAddress(ADMIN_SLOT); } function setAdmin(address _newAdmin) internal { setAddress(ADMIN_SLOT, _newAdmin); } function getPendingAdmin() internal view returns (address) { return getAddress(PENDING_ADMIN_SLOT); } function setPendingAdmin(address _newPendingAdmin) internal { setAddress(PENDING_ADMIN_SLOT, _newPendingAdmin); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant TREASURY_SLOT = keccak256("StakingContract.treasury"); function getTreasury() internal view returns (address) { return getAddress(TREASURY_SLOT); } function setTreasury(address _newTreasury) internal { setAddress(TREASURY_SLOT, _newTreasury); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant DEPOSIT_CONTRACT_SLOT = keccak256("StakingContract.depositContract"); function getDepositContract() internal view returns (address) { return getAddress(DEPOSIT_CONTRACT_SLOT); } function setDepositContract(address _newDepositContract) internal { setAddress(DEPOSIT_CONTRACT_SLOT, _newDepositContract); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant OPERATORS_SLOT = keccak256("StakingContract.operators"); struct OperatorInfo { address operator; address feeRecipient; uint256 limit; bytes[] publicKeys; bytes[] signatures; bool deactivated; } struct OperatorsSlot { OperatorInfo[] value; } function getOperators() internal pure returns (OperatorsSlot storage p) { bytes32 slot = OPERATORS_SLOT; assembly { p.slot := slot } } /* ======================================== =========================================== =========================================*/ /// Validator funding information is stored in a packed fashion /// We fit 4 vfi per storage slot. /// Each vfi is stored in 64 bits, with the following layout: /// 32 bits for the number of available keys /// 32 bits for the number of funded keys uint256 internal constant FUNDED_OFFSET = 32; bytes32 internal constant VALIDATORS_FUNDING_INFO_SLOT = keccak256("StakingContract.validatorsFundingInfo"); struct ValidatorsFundingInfo { uint32 availableKeys; uint32 funded; } struct UintToUintMappingSlot { mapping(uint256 => uint256) value; } function getValidatorsFundingInfo(uint256 _index) internal view returns (ValidatorsFundingInfo memory vfi) { UintToUintMappingSlot storage p; bytes32 slot = VALIDATORS_FUNDING_INFO_SLOT; assembly { p.slot := slot } uint256 slotIndex = _index >> 2; // divide by 4 uint256 innerIndex = (_index & 3) << 6; // modulo 4, multiply by 64 uint256 value = p.value[slotIndex] >> innerIndex; vfi.availableKeys = uint32(value); vfi.funded = uint32(value >> FUNDED_OFFSET); } function setValidatorsFundingInfo( uint256 _index, uint32 _availableKeys, uint32 _funded ) internal { UintToUintMappingSlot storage p; bytes32 slot = VALIDATORS_FUNDING_INFO_SLOT; assembly { p.slot := slot } uint256 slotIndex = _index >> 2; // divide by 4 uint256 innerIndex = (_index & 3) << 6; // modulo 4, multiply by 64 p.value[slotIndex] = (p.value[slotIndex] & (~(uint256(0xFFFFFFFFFFFFFFFF) << innerIndex))) | // clear the bits we want to set ((uint256(_availableKeys) | (uint256(_funded) << FUNDED_OFFSET)) << innerIndex); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant TOTAL_AVAILABLE_VALIDATORS_SLOT = keccak256("StakingContract.totalAvailableValidators"); function getTotalAvailableValidators() internal view returns (uint256) { return getUint256(TOTAL_AVAILABLE_VALIDATORS_SLOT); } function setTotalAvailableValidators(uint256 _newTotal) internal { setUint256(TOTAL_AVAILABLE_VALIDATORS_SLOT, _newTotal); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant WITHDRAWERS_SLOT = keccak256("StakingContract.withdrawers"); struct WithdrawersSlot { mapping(bytes32 => address) value; } function getWithdrawers() internal pure returns (WithdrawersSlot storage p) { bytes32 slot = WITHDRAWERS_SLOT; assembly { p.slot := slot } } /* ======================================== =========================================== =========================================*/ struct OperatorIndex { bool enabled; uint32 operatorIndex; } struct OperatorIndexPerValidatorSlot { mapping(bytes32 => OperatorIndex) value; } bytes32 internal constant OPERATOR_INDEX_PER_VALIDATOR_SLOT = keccak256("StakingContract.operatorIndexPerValidator"); function getOperatorIndexPerValidator() internal pure returns (OperatorIndexPerValidatorSlot storage p) { bytes32 slot = OPERATOR_INDEX_PER_VALIDATOR_SLOT; assembly { p.slot := slot } } /* ======================================== =========================================== =========================================*/ bytes32 internal constant GLOBAL_FEE_SLOT = keccak256("StakingContract.globalFee"); function getGlobalFee() internal view returns (uint256) { return getUint256(GLOBAL_FEE_SLOT); } function setGlobalFee(uint256 _newTreasuryFee) internal { setUint256(GLOBAL_FEE_SLOT, _newTreasuryFee); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant OPERATOR_FEE_SLOT = keccak256("StakingContract.operatorFee"); function getOperatorFee() internal view returns (uint256) { return getUint256(OPERATOR_FEE_SLOT); } function setOperatorFee(uint256 _newOperatorFee) internal { setUint256(OPERATOR_FEE_SLOT, _newOperatorFee); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant EL_DISPATCHER_SLOT = keccak256("StakingContract.executionLayerDispatcher"); function getELDispatcher() internal view returns (address) { return getAddress(EL_DISPATCHER_SLOT); } function setELDispatcher(address _newElDispatcher) internal { setAddress(EL_DISPATCHER_SLOT, _newElDispatcher); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant CL_DISPATCHER_SLOT = keccak256("StakingContract.consensusLayerDispatcher"); function getCLDispatcher() internal view returns (address) { return getAddress(CL_DISPATCHER_SLOT); } function setCLDispatcher(address _newClDispatcher) internal { setAddress(CL_DISPATCHER_SLOT, _newClDispatcher); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant FEE_RECIPIENT_IMPLEMENTATION_SLOT = keccak256("StakingContract.feeRecipientImplementation"); function getFeeRecipientImplementation() internal view returns (address) { return getAddress(FEE_RECIPIENT_IMPLEMENTATION_SLOT); } function setFeeRecipientImplementation(address _newFeeRecipientImplementation) internal { setAddress(FEE_RECIPIENT_IMPLEMENTATION_SLOT, _newFeeRecipientImplementation); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant WITHDRAWER_CUSTOMIZATION_ENABLED_SLOT = keccak256("StakingContract.withdrawerCustomizationEnabled"); function getWithdrawerCustomizationEnabled() internal view returns (bool) { return getBool(WITHDRAWER_CUSTOMIZATION_ENABLED_SLOT); } function setWithdrawerCustomizationEnabled(bool _enabled) internal { setBool(WITHDRAWER_CUSTOMIZATION_ENABLED_SLOT, _enabled); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant EXIT_REQUEST_MAPPING_SLOT = bytes32(uint256(keccak256("StakingContract.exitRequest")) - 1); struct ExitRequestMap { mapping(bytes32 => bool) value; } function getExitRequestMap() internal pure returns (ExitRequestMap storage p) { bytes32 slot = EXIT_REQUEST_MAPPING_SLOT; assembly { p.slot := slot } } /* ======================================== =========================================== =========================================*/ bytes32 internal constant WITHDRAWN_MAPPING_SLOT = bytes32(uint256(keccak256("StakingContract.withdrawn")) - 1); struct WithdrawnMap { mapping(bytes32 => bool) value; } function getWithdrawnMap() internal pure returns (WithdrawnMap storage p) { bytes32 slot = WITHDRAWN_MAPPING_SLOT; assembly { p.slot := slot } } /* ======================================== =========================================== =========================================*/ bytes32 internal constant GLOBAL_COMMISSION_LIMIT_SLOT = bytes32(uint256(keccak256("StakingContract.globalCommissionLimit")) - 1); function getGlobalCommissionLimit() internal view returns (uint256) { return getUint256(GLOBAL_COMMISSION_LIMIT_SLOT); } function setGlobalCommissionLimit(uint256 value) internal { setUint256(GLOBAL_COMMISSION_LIMIT_SLOT, value); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant OPERATOR_COMMISSION_LIMIT_SLOT = bytes32(uint256(keccak256("StakingContract.operatorCommissionLimit")) - 1); function getOperatorCommissionLimit() internal view returns (uint256) { return getUint256(OPERATOR_COMMISSION_LIMIT_SLOT); } function setOperatorCommissionLimit(uint256 value) internal { setUint256(OPERATOR_COMMISSION_LIMIT_SLOT, value); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant DEPOSIT_STOPPED_SLOT = bytes32(uint256(keccak256("StakingContract.depositStopped")) - 1); function getDepositStopped() internal view returns (bool) { return getBool(DEPOSIT_STOPPED_SLOT); } function setDepositStopped(bool val) internal { setBool(DEPOSIT_STOPPED_SLOT, val); } /* ======================================== =========================================== =========================================*/ bytes32 internal constant LAST_VALIDATOR_EDIT_SLOT = bytes32(uint256(keccak256("StakingContract.lastValidatorsEdit")) - 1); function getLastValidatorEdit() internal view returns (uint256) { return getUint256(LAST_VALIDATOR_EDIT_SLOT); } function setLastValidatorEdit(uint256 value) internal { setUint256(LAST_VALIDATOR_EDIT_SLOT, value); } }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.10; import "./libs/BytesLib.sol"; import "./interfaces/IFeeRecipient.sol"; import "./interfaces/IDepositContract.sol"; import "./libs/StakingContractStorageLib.sol"; import "@openzeppelin/contracts/proxy/Clones.sol"; /// @title Ethereum Staking Contract /// @author Kiln /// @notice You can use this contract to store validator keys and have users fund them and trigger deposits. contract StakingContract { using StakingContractStorageLib for bytes32; uint256 internal constant EXECUTION_LAYER_SALT_PREFIX = 0; uint256 internal constant CONSENSUS_LAYER_SALT_PREFIX = 1; uint256 public constant SIGNATURE_LENGTH = 96; uint256 public constant PUBLIC_KEY_LENGTH = 48; uint256 public constant DEPOSIT_SIZE = 32 ether; // this is the equivalent of Uint256Lib.toLittleEndian64(DEPOSIT_SIZE / 1000000000 wei); uint256 constant DEPOSIT_SIZE_AMOUNT_LITTLEENDIAN64 = 0x0040597307000000000000000000000000000000000000000000000000000000; uint256 internal constant BASIS_POINTS = 10_000; uint256 internal constant WITHDRAWAL_CREDENTIAL_PREFIX_01 = 0x0100000000000000000000000000000000000000000000000000000000000000; error Forbidden(); error InvalidFee(); error Deactivated(); error NoOperators(); error InvalidCall(); error Unauthorized(); error DepositFailure(); error DepositsStopped(); error InvalidArgument(); error UnsortedIndexes(); error InvalidPublicKeys(); error InvalidSignatures(); error InvalidWithdrawer(); error InvalidZeroAddress(); error AlreadyInitialized(); error InvalidDepositValue(); error NotEnoughValidators(); error InvalidValidatorCount(); error DuplicateValidatorKey(bytes); error FundedValidatorDeletionAttempt(); error OperatorLimitTooHigh(uint256 limit, uint256 keyCount); error MaximumOperatorCountAlreadyReached(); error LastEditAfterSnapshot(); error PublicKeyNotInContract(); struct ValidatorAllocationCache { bool used; uint8 operatorIndex; uint32 funded; uint32 toDeposit; uint32 available; } event Deposit(address indexed caller, address indexed withdrawer, bytes publicKey, bytes signature); event ValidatorKeysAdded(uint256 indexed operatorIndex, bytes publicKeys, bytes signatures); event ValidatorKeyRemoved(uint256 indexed operatorIndex, bytes publicKey); event ChangedWithdrawer(bytes publicKey, address newWithdrawer); event ChangedOperatorLimit(uint256 operatorIndex, uint256 limit); event ChangedTreasury(address newTreasury); event ChangedGlobalFee(uint256 newGlobalFee); event ChangedOperatorFee(uint256 newOperatorFee); event ChangedAdmin(address newAdmin); event ChangedDepositsStopped(bool isStopped); event NewOperator(address operatorAddress, address feeRecipientAddress, uint256 index); event ChangedOperatorAddresses(uint256 operatorIndex, address operatorAddress, address feeRecipientAddress); event DeactivatedOperator(uint256 _operatorIndex); event ActivatedOperator(uint256 _operatorIndex); event SetWithdrawerCustomizationStatus(bool _status); event ExitRequest(address caller, bytes pubkey); event ValidatorsEdited(uint256 blockNumber); /// @notice Ensures an initialisation call has been called only once per _version value /// @param _version The current initialisation value modifier init(uint256 _version) { if (_version != StakingContractStorageLib.getVersion() + 1) { revert AlreadyInitialized(); } StakingContractStorageLib.setVersion(_version); _; } /// @notice Ensures that the caller is the admin modifier onlyAdmin() { if (msg.sender != StakingContractStorageLib.getAdmin()) { revert Unauthorized(); } _; } /// @notice Ensures that the caller is the admin or the operator modifier onlyActiveOperatorOrAdmin(uint256 _operatorIndex) { if (msg.sender == StakingContractStorageLib.getAdmin()) { _; } else { _onlyActiveOperator(_operatorIndex); _; } } /// @notice Ensures that the caller is the admin modifier onlyActiveOperator(uint256 _operatorIndex) { _onlyActiveOperator(_operatorIndex); _; } /// @notice Ensures that the caller is the operator fee recipient modifier onlyActiveOperatorFeeRecipient(uint256 _operatorIndex) { StakingContractStorageLib.OperatorInfo storage operatorInfo = StakingContractStorageLib.getOperators().value[ _operatorIndex ]; if (operatorInfo.deactivated) { revert Deactivated(); } if (msg.sender != operatorInfo.feeRecipient) { revert Unauthorized(); } _; } /// @notice Explicit deposit method using msg.sender /// @dev A multiple of 32 ETH should be sent function deposit() external payable { _deposit(); } /// @notice Implicit deposit method /// @dev A multiple of 32 ETH should be sent /// @dev The withdrawer is set to the message sender address receive() external payable { _deposit(); } /// @notice Fallback detection /// @dev Fails on any call that fallbacks fallback() external payable { revert InvalidCall(); } function initialize_1( address _admin, address _treasury, address _depositContract, address _elDispatcher, address _clDispatcher, address _feeRecipientImplementation, uint256 _globalFee, uint256 _operatorFee, uint256 globalCommissionLimitBPS, uint256 operatorCommissionLimitBPS ) external init(1) { _checkAddress(_admin); StakingContractStorageLib.setAdmin(_admin); _checkAddress(_treasury); StakingContractStorageLib.setTreasury(_treasury); if (_globalFee > BASIS_POINTS) { revert InvalidFee(); } StakingContractStorageLib.setGlobalFee(_globalFee); if (_operatorFee > BASIS_POINTS) { revert InvalidFee(); } StakingContractStorageLib.setOperatorFee(_operatorFee); _checkAddress(_elDispatcher); StakingContractStorageLib.setELDispatcher(_elDispatcher); _checkAddress(_clDispatcher); StakingContractStorageLib.setCLDispatcher(_clDispatcher); _checkAddress(_depositContract); StakingContractStorageLib.setDepositContract(_depositContract); _checkAddress(_feeRecipientImplementation); StakingContractStorageLib.setFeeRecipientImplementation(_feeRecipientImplementation); initialize_2(globalCommissionLimitBPS, operatorCommissionLimitBPS); } function initialize_2(uint256 globalCommissionLimitBPS, uint256 operatorCommissionLimitBPS) public init(2) { if (globalCommissionLimitBPS > BASIS_POINTS) { revert InvalidFee(); } StakingContractStorageLib.setGlobalCommissionLimit(globalCommissionLimitBPS); if (operatorCommissionLimitBPS > BASIS_POINTS) { revert InvalidFee(); } StakingContractStorageLib.setOperatorCommissionLimit(operatorCommissionLimitBPS); } /// @notice Changes the behavior of the withdrawer customization logic /// @param _enabled True to allow users to customize the withdrawer function setWithdrawerCustomizationEnabled(bool _enabled) external onlyAdmin { StakingContractStorageLib.setWithdrawerCustomizationEnabled(_enabled); emit SetWithdrawerCustomizationStatus(_enabled); } /// @notice Retrieve system admin function getAdmin() external view returns (address) { return StakingContractStorageLib.getAdmin(); } /// @notice Set new treasury /// @dev Only callable by admin /// @param _newTreasury New Treasury address function setTreasury(address _newTreasury) external onlyAdmin { emit ChangedTreasury(_newTreasury); StakingContractStorageLib.setTreasury(_newTreasury); } /// @notice Retrieve system treasury function getTreasury() external view returns (address) { return StakingContractStorageLib.getTreasury(); } /// @notice Retrieve the global fee function getGlobalFee() external view returns (uint256) { return StakingContractStorageLib.getGlobalFee(); } /// @notice Retrieve the operator fee function getOperatorFee() external view returns (uint256) { return StakingContractStorageLib.getOperatorFee(); } /// @notice Compute the Execution Layer Fee recipient address for a given validator public key /// @param _publicKey Validator to get the recipient function getELFeeRecipient(bytes calldata _publicKey) external view returns (address) { return _getDeterministicReceiver(_publicKey, EXECUTION_LAYER_SALT_PREFIX); } /// @notice Compute the Consensus Layer Fee recipient address for a given validator public key /// @param _publicKey Validator to get the recipient function getCLFeeRecipient(bytes calldata _publicKey) external view returns (address) { return _getDeterministicReceiver(_publicKey, CONSENSUS_LAYER_SALT_PREFIX); } /// @notice Retrieve the Execution & Consensus Layer Fee operator recipient for a given public key function getOperatorFeeRecipient(bytes32 pubKeyRoot) external view returns (address) { if (StakingContractStorageLib.getOperatorIndexPerValidator().value[pubKeyRoot].enabled == false) { revert PublicKeyNotInContract(); } return StakingContractStorageLib .getOperators() .value[StakingContractStorageLib.getOperatorIndexPerValidator().value[pubKeyRoot].operatorIndex] .feeRecipient; } /// @notice Retrieve withdrawer of public key /// @notice In case the validator is not enabled, it will return address(0) /// @param _publicKey Public Key to check function getWithdrawer(bytes calldata _publicKey) external view returns (address) { return _getWithdrawer(_getPubKeyRoot(_publicKey)); } /// @notice Retrieve withdrawer of public key root /// @notice In case the validator is not enabled, it will return address(0) /// @param _publicKeyRoot Hash of the public key function getWithdrawerFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (address) { return _getWithdrawer(_publicKeyRoot); } /// @notice Retrieve whether the validator exit has been requested /// @notice In case the validator is not enabled, it will return false /// @param _publicKeyRoot Public Key Root to check function getExitRequestedFromRoot(bytes32 _publicKeyRoot) external view returns (bool) { return _getExitRequest(_publicKeyRoot); } /// @notice Return true if the validator already went through the exit logic /// @notice In case the validator is not enabled, it will return false /// @param _publicKeyRoot Public Key Root of the validator function getWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (bool) { return StakingContractStorageLib.getWithdrawnMap().value[_publicKeyRoot]; } /// @notice Retrieve the enabled status of public key root, true if the key is in the contract /// @param _publicKeyRoot Hash of the public key function getEnabledFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (bool) { return StakingContractStorageLib.getOperatorIndexPerValidator().value[_publicKeyRoot].enabled; } /// @notice Allows the CLDispatcher to signal a validator went through the exit logic /// @param _publicKeyRoot Public Key Root of the validator function toggleWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external { if (msg.sender != StakingContractStorageLib.getCLDispatcher()) { revert Unauthorized(); } StakingContractStorageLib.getWithdrawnMap().value[_publicKeyRoot] = true; } /// @notice Returns false if the users can deposit, true if deposits are stopped function getDepositsStopped() external view returns (bool) { return StakingContractStorageLib.getDepositStopped(); } /// @notice Retrieve operator details /// @param _operatorIndex Operator index function getOperator(uint256 _operatorIndex) external view returns ( address operatorAddress, address feeRecipientAddress, uint256 limit, uint256 keys, uint256 funded, uint256 available, bool deactivated ) { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); if (_operatorIndex < operators.value.length) { StakingContractStorageLib.ValidatorsFundingInfo memory _operatorInfo = StakingContractStorageLib .getValidatorsFundingInfo(_operatorIndex); StakingContractStorageLib.OperatorInfo storage _operator = operators.value[_operatorIndex]; (operatorAddress, feeRecipientAddress, limit, keys, deactivated) = ( _operator.operator, _operator.feeRecipient, _operator.limit, _operator.publicKeys.length, _operator.deactivated ); (funded, available) = (_operatorInfo.funded, _operatorInfo.availableKeys); } } /// @notice Get details about a validator /// @param _operatorIndex Index of the operator running the validator /// @param _validatorIndex Index of the validator function getValidator(uint256 _operatorIndex, uint256 _validatorIndex) external view returns ( bytes memory publicKey, bytes memory signature, address withdrawer, bool funded ) { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); publicKey = operators.value[_operatorIndex].publicKeys[_validatorIndex]; signature = operators.value[_operatorIndex].signatures[_validatorIndex]; withdrawer = _getWithdrawer(_getPubKeyRoot(publicKey)); funded = _validatorIndex < StakingContractStorageLib.getValidatorsFundingInfo(_operatorIndex).funded; } /// @notice Get the total available keys that are ready to be used for deposits function getAvailableValidatorCount() external view returns (uint256) { return StakingContractStorageLib.getTotalAvailableValidators(); } /// @notice Set new admin /// @dev Only callable by admin /// @param _newAdmin New Administrator address function transferOwnership(address _newAdmin) external onlyAdmin { StakingContractStorageLib.setPendingAdmin(_newAdmin); } /// @notice New admin must accept its role by calling this method /// @dev Only callable by new admin function acceptOwnership() external { address newAdmin = StakingContractStorageLib.getPendingAdmin(); if (msg.sender != newAdmin) { revert Unauthorized(); } StakingContractStorageLib.setAdmin(newAdmin); StakingContractStorageLib.setPendingAdmin(address(0)); emit ChangedAdmin(newAdmin); } /// @notice Get the new admin's address previously set for an ownership transfer function getPendingAdmin() external view returns (address) { return StakingContractStorageLib.getPendingAdmin(); } /// @notice Add new operator /// @dev Only callable by admin /// @param _operatorAddress Operator address allowed to add / remove validators /// @param _feeRecipientAddress Privileged operator address used to manage rewards and operator addresses function addOperator(address _operatorAddress, address _feeRecipientAddress) external onlyAdmin returns (uint256) { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); StakingContractStorageLib.OperatorInfo memory newOperator; if (operators.value.length == 1) { revert MaximumOperatorCountAlreadyReached(); } newOperator.operator = _operatorAddress; newOperator.feeRecipient = _feeRecipientAddress; operators.value.push(newOperator); uint256 operatorIndex = operators.value.length - 1; emit NewOperator(_operatorAddress, _feeRecipientAddress, operatorIndex); return operatorIndex; } /// @notice Set new operator addresses (operations and reward management) /// @dev Only callable by fee recipient address manager /// @param _operatorIndex Index of the operator to update /// @param _operatorAddress New operator address for operations management /// @param _feeRecipientAddress New operator address for reward management function setOperatorAddresses( uint256 _operatorIndex, address _operatorAddress, address _feeRecipientAddress ) external onlyActiveOperatorFeeRecipient(_operatorIndex) { _checkAddress(_operatorAddress); _checkAddress(_feeRecipientAddress); StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); operators.value[_operatorIndex].operator = _operatorAddress; operators.value[_operatorIndex].feeRecipient = _feeRecipientAddress; emit ChangedOperatorAddresses(_operatorIndex, _operatorAddress, _feeRecipientAddress); } /// @notice Set withdrawer for public key /// @dev Only callable by current public key withdrawer /// @param _publicKey Public key to change withdrawer /// @param _newWithdrawer New withdrawer address function setWithdrawer(bytes calldata _publicKey, address _newWithdrawer) external { if (!StakingContractStorageLib.getWithdrawerCustomizationEnabled()) { revert Forbidden(); } _checkAddress(_newWithdrawer); bytes32 pubkeyRoot = _getPubKeyRoot(_publicKey); StakingContractStorageLib.WithdrawersSlot storage withdrawers = StakingContractStorageLib.getWithdrawers(); if (withdrawers.value[pubkeyRoot] != msg.sender) { revert Unauthorized(); } emit ChangedWithdrawer(_publicKey, _newWithdrawer); withdrawers.value[pubkeyRoot] = _newWithdrawer; } /// @notice Set operator staking limits /// @dev Only callable by admin /// @dev Limit should not exceed the validator key count of the operator /// @dev Keys should be registered before limit is increased /// @dev Allows all keys to be verified by the system admin before limit is increased /// @param _operatorIndex Operator Index /// @param _limit New staking limit /// @param _snapshot Block number at which verification was done function setOperatorLimit( uint256 _operatorIndex, uint256 _limit, uint256 _snapshot ) external onlyAdmin { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); if (operators.value[_operatorIndex].deactivated) { revert Deactivated(); } uint256 publicKeyCount = operators.value[_operatorIndex].publicKeys.length; if (publicKeyCount < _limit) { revert OperatorLimitTooHigh(_limit, publicKeyCount); } if ( operators.value[_operatorIndex].limit < _limit && StakingContractStorageLib.getLastValidatorEdit() > _snapshot ) { revert LastEditAfterSnapshot(); } operators.value[_operatorIndex].limit = _limit; _updateAvailableValidatorCount(_operatorIndex); emit ChangedOperatorLimit(_operatorIndex, _limit); } /// @notice Deactivates an operator and changes the fee recipient address and the staking limit /// @param _operatorIndex Operator Index /// @param _temporaryFeeRecipient Temporary address to receive funds decided by the system admin function deactivateOperator(uint256 _operatorIndex, address _temporaryFeeRecipient) external onlyAdmin { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); operators.value[_operatorIndex].limit = 0; emit ChangedOperatorLimit(_operatorIndex, 0); operators.value[_operatorIndex].deactivated = true; emit DeactivatedOperator(_operatorIndex); operators.value[_operatorIndex].feeRecipient = _temporaryFeeRecipient; emit ChangedOperatorAddresses(_operatorIndex, operators.value[_operatorIndex].operator, _temporaryFeeRecipient); _updateAvailableValidatorCount(_operatorIndex); } /// @notice Activates an operator, without changing its 0 staking limit /// @param _operatorIndex Operator Index /// @param _newFeeRecipient Sets the fee recipient address function activateOperator(uint256 _operatorIndex, address _newFeeRecipient) external onlyAdmin { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); operators.value[_operatorIndex].deactivated = false; emit ActivatedOperator(_operatorIndex); operators.value[_operatorIndex].feeRecipient = _newFeeRecipient; emit ChangedOperatorAddresses(_operatorIndex, operators.value[_operatorIndex].operator, _newFeeRecipient); } /// @notice Change the Operator fee /// @param _operatorFee Fee in Basis Point function setOperatorFee(uint256 _operatorFee) external onlyAdmin { if (_operatorFee > StakingContractStorageLib.getOperatorCommissionLimit()) { revert InvalidFee(); } StakingContractStorageLib.setOperatorFee(_operatorFee); emit ChangedOperatorFee(_operatorFee); } /// @notice Change the Global fee /// @param _globalFee Fee in Basis Point function setGlobalFee(uint256 _globalFee) external onlyAdmin { if (_globalFee > StakingContractStorageLib.getGlobalCommissionLimit()) { revert InvalidFee(); } StakingContractStorageLib.setGlobalFee(_globalFee); emit ChangedGlobalFee(_globalFee); } /// @notice Add new validator public keys and signatures /// @dev Only callable by operator /// @param _operatorIndex Operator Index /// @param _keyCount Number of keys added /// @param _publicKeys Concatenated _keyCount public keys /// @param _signatures Concatenated _keyCount signatures function addValidators( uint256 _operatorIndex, uint256 _keyCount, bytes calldata _publicKeys, bytes calldata _signatures ) external onlyActiveOperator(_operatorIndex) { if (_keyCount == 0) { revert InvalidArgument(); } if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0 || _publicKeys.length / PUBLIC_KEY_LENGTH != _keyCount) { revert InvalidPublicKeys(); } if (_signatures.length % SIGNATURE_LENGTH != 0 || _signatures.length / SIGNATURE_LENGTH != _keyCount) { revert InvalidSignatures(); } StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); StakingContractStorageLib.OperatorIndexPerValidatorSlot storage operatorIndexPerValidator = StakingContractStorageLib.getOperatorIndexPerValidator(); for (uint256 i; i < _keyCount; ) { bytes memory publicKey = BytesLib.slice(_publicKeys, i * PUBLIC_KEY_LENGTH, PUBLIC_KEY_LENGTH); bytes memory signature = BytesLib.slice(_signatures, i * SIGNATURE_LENGTH, SIGNATURE_LENGTH); operators.value[_operatorIndex].publicKeys.push(publicKey); operators.value[_operatorIndex].signatures.push(signature); bytes32 pubKeyRoot = _getPubKeyRoot(publicKey); if (operatorIndexPerValidator.value[pubKeyRoot].enabled) { revert DuplicateValidatorKey(publicKey); } operatorIndexPerValidator.value[pubKeyRoot] = StakingContractStorageLib.OperatorIndex({ enabled: true, operatorIndex: uint32(_operatorIndex) }); unchecked { ++i; } } emit ValidatorKeysAdded(_operatorIndex, _publicKeys, _signatures); _updateLastValidatorsEdit(); _updateAvailableValidatorCount(_operatorIndex); } /// @notice Remove unfunded validators /// @dev Only callable by operator /// @dev Indexes should be provided in decreasing order /// @dev The limit will be set to the lowest removed operator index to ensure all changes above the /// lowest removed validator key are verified by the system administrator /// @param _operatorIndex Operator Index /// @param _indexes List of indexes to delete, in decreasing order function removeValidators(uint256 _operatorIndex, uint256[] calldata _indexes) external onlyActiveOperatorOrAdmin(_operatorIndex) { if (_indexes.length == 0) { revert InvalidArgument(); } StakingContractStorageLib.ValidatorsFundingInfo memory operatorInfo = StakingContractStorageLib .getValidatorsFundingInfo(_operatorIndex); StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); StakingContractStorageLib.OperatorIndexPerValidatorSlot storage operatorIndexPerValidator = StakingContractStorageLib.getOperatorIndexPerValidator(); if (_indexes[_indexes.length - 1] < operatorInfo.funded) { revert FundedValidatorDeletionAttempt(); } for (uint256 i; i < _indexes.length; ) { if (i > 0 && _indexes[i] >= _indexes[i - 1]) { revert UnsortedIndexes(); } bytes32 pubKeyRoot = _getPubKeyRoot(operators.value[_operatorIndex].publicKeys[_indexes[i]]); operatorIndexPerValidator.value[pubKeyRoot].enabled = false; operatorIndexPerValidator.value[pubKeyRoot].operatorIndex = 0; emit ValidatorKeyRemoved(_operatorIndex, operators.value[_operatorIndex].publicKeys[_indexes[i]]); if (_indexes[i] == operators.value[_operatorIndex].publicKeys.length - 1) { operators.value[_operatorIndex].publicKeys.pop(); operators.value[_operatorIndex].signatures.pop(); } else { operators.value[_operatorIndex].publicKeys[_indexes[i]] = operators.value[_operatorIndex].publicKeys[ operators.value[_operatorIndex].publicKeys.length - 1 ]; operators.value[_operatorIndex].publicKeys.pop(); operators.value[_operatorIndex].signatures[_indexes[i]] = operators.value[_operatorIndex].signatures[ operators.value[_operatorIndex].signatures.length - 1 ]; operators.value[_operatorIndex].signatures.pop(); } unchecked { ++i; } } if (_indexes[_indexes.length - 1] < operators.value[_operatorIndex].limit) { operators.value[_operatorIndex].limit = _indexes[_indexes.length - 1]; emit ChangedOperatorLimit(_operatorIndex, _indexes[_indexes.length - 1]); } _updateLastValidatorsEdit(); _updateAvailableValidatorCount(_operatorIndex); } /// @notice Withdraw the Execution Layer Fee for given validators public keys /// @dev Funds are sent to the withdrawer account /// @dev This method is public on purpose /// @param _publicKeys Validators to withdraw Execution Layer Fees from function batchWithdrawELFee(bytes calldata _publicKeys) external { if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) { revert InvalidPublicKeys(); } for (uint256 i = 0; i < _publicKeys.length; ) { bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH); _onlyWithdrawerOrAdmin(publicKey); _deployAndWithdraw(publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher()); unchecked { i += PUBLIC_KEY_LENGTH; } } } /// @notice Withdraw the Consensus Layer Fee for given validators public keys /// @dev Funds are sent to the withdrawer account /// @dev This method is public on purpose /// @param _publicKeys Validators to withdraw Consensus Layer Fees from function batchWithdrawCLFee(bytes calldata _publicKeys) external { if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) { revert InvalidPublicKeys(); } for (uint256 i = 0; i < _publicKeys.length; ) { bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH); _onlyWithdrawerOrAdmin(publicKey); _deployAndWithdraw(publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher()); unchecked { i += PUBLIC_KEY_LENGTH; } } } /// @notice Withdraw both Consensus and Execution Layer Fees for given validators public keys /// @dev Funds are sent to the withdrawer account /// @param _publicKeys Validators to withdraw fees from function batchWithdraw(bytes calldata _publicKeys) external { if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) { revert InvalidPublicKeys(); } for (uint256 i = 0; i < _publicKeys.length; ) { bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH); _onlyWithdrawerOrAdmin(publicKey); _deployAndWithdraw(publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher()); _deployAndWithdraw(publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher()); unchecked { i += PUBLIC_KEY_LENGTH; } } } /// @notice Withdraw the Execution Layer Fee for a given validator public key /// @dev Funds are sent to the withdrawer account /// @param _publicKey Validator to withdraw Execution Layer Fees from function withdrawELFee(bytes calldata _publicKey) external { _onlyWithdrawerOrAdmin(_publicKey); _deployAndWithdraw(_publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher()); } /// @notice Withdraw the Consensus Layer Fee for a given validator public key /// @dev Funds are sent to the withdrawer account /// @param _publicKey Validator to withdraw Consensus Layer Fees from function withdrawCLFee(bytes calldata _publicKey) external { _onlyWithdrawerOrAdmin(_publicKey); _deployAndWithdraw(_publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher()); } /// @notice Withdraw both Consensus and Execution Layer Fee for a given validator public key /// @dev Reverts if any is null /// @param _publicKey Validator to withdraw Execution and Consensus Layer Fees from function withdraw(bytes calldata _publicKey) external { _onlyWithdrawerOrAdmin(_publicKey); _deployAndWithdraw(_publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher()); _deployAndWithdraw(_publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher()); } function requestValidatorsExit(bytes calldata _publicKeys) external { if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) { revert InvalidPublicKeys(); } for (uint256 i = 0; i < _publicKeys.length; ) { bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH); bytes32 pubKeyRoot = _getPubKeyRoot(publicKey); address withdrawer = _getWithdrawer(pubKeyRoot); if (msg.sender != withdrawer) { revert Unauthorized(); } _setExitRequest(pubKeyRoot, true); emit ExitRequest(withdrawer, publicKey); unchecked { i += PUBLIC_KEY_LENGTH; } } } /// @notice Utility to stop or allow deposits function setDepositsStopped(bool val) external onlyAdmin { emit ChangedDepositsStopped(val); StakingContractStorageLib.setDepositStopped(val); } /// ██ ███ ██ ████████ ███████ ██████ ███ ██ █████ ██ /// ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ /// ██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ███████ ██ /// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ /// ██ ██ ████ ██ ███████ ██ ██ ██ ████ ██ ██ ███████ function _onlyWithdrawerOrAdmin(bytes memory _publicKey) internal view { if ( msg.sender != _getWithdrawer(_getPubKeyRoot(_publicKey)) && StakingContractStorageLib.getAdmin() != msg.sender ) { revert InvalidWithdrawer(); } } function _onlyActiveOperator(uint256 _operatorIndex) internal view { StakingContractStorageLib.OperatorInfo storage operatorInfo = StakingContractStorageLib.getOperators().value[ _operatorIndex ]; if (operatorInfo.deactivated) { revert Deactivated(); } if (msg.sender != operatorInfo.operator) { revert Unauthorized(); } } function _getPubKeyRoot(bytes memory _publicKey) internal pure returns (bytes32) { return sha256(abi.encodePacked(_publicKey, bytes16(0))); } function _getWithdrawer(bytes32 _publicKeyRoot) internal view returns (address) { return StakingContractStorageLib.getWithdrawers().value[_publicKeyRoot]; } function _getExitRequest(bytes32 _publicKeyRoot) internal view returns (bool) { return StakingContractStorageLib.getExitRequestMap().value[_publicKeyRoot]; } function _setExitRequest(bytes32 _publicKeyRoot, bool _value) internal { StakingContractStorageLib.getExitRequestMap().value[_publicKeyRoot] = _value; } function _updateAvailableValidatorCount(uint256 _operatorIndex) internal { StakingContractStorageLib.ValidatorsFundingInfo memory validatorFundingInfo = StakingContractStorageLib .getValidatorsFundingInfo(_operatorIndex); StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); uint32 oldAvailableCount = validatorFundingInfo.availableKeys; uint32 newAvailableCount = 0; uint256 cap = operators.value[_operatorIndex].limit; if (cap <= validatorFundingInfo.funded) { StakingContractStorageLib.setValidatorsFundingInfo(_operatorIndex, 0, validatorFundingInfo.funded); } else { newAvailableCount = uint32(cap - validatorFundingInfo.funded); StakingContractStorageLib.setValidatorsFundingInfo( _operatorIndex, newAvailableCount, validatorFundingInfo.funded ); } if (oldAvailableCount != newAvailableCount) { StakingContractStorageLib.setTotalAvailableValidators( (StakingContractStorageLib.getTotalAvailableValidators() - oldAvailableCount) + newAvailableCount ); } } function _updateLastValidatorsEdit() internal { StakingContractStorageLib.setLastValidatorEdit(block.number); emit ValidatorsEdited(block.number); } function _addressToWithdrawalCredentials(address _recipient) internal pure returns (bytes32) { return bytes32(uint256(uint160(_recipient)) + WITHDRAWAL_CREDENTIAL_PREFIX_01); } function _depositValidatorsOfOperator(uint256 _operatorIndex, uint256 _validatorCount) internal { StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); StakingContractStorageLib.OperatorInfo storage operator = operators.value[_operatorIndex]; StakingContractStorageLib.ValidatorsFundingInfo memory vfi = StakingContractStorageLib.getValidatorsFundingInfo( _operatorIndex ); for (uint256 i = vfi.funded; i < vfi.funded + _validatorCount; ) { bytes memory publicKey = operator.publicKeys[i]; bytes memory signature = operator.signatures[i]; address consensusLayerRecipient = _getDeterministicReceiver(publicKey, CONSENSUS_LAYER_SALT_PREFIX); bytes32 withdrawalCredentials = _addressToWithdrawalCredentials(consensusLayerRecipient); bytes32 pubkeyRoot = _getPubKeyRoot(publicKey); _depositValidator(publicKey, pubkeyRoot, signature, withdrawalCredentials); StakingContractStorageLib.getWithdrawers().value[pubkeyRoot] = msg.sender; emit Deposit(msg.sender, msg.sender, publicKey, signature); unchecked { ++i; } } StakingContractStorageLib.setValidatorsFundingInfo( _operatorIndex, uint32(vfi.availableKeys - _validatorCount), uint32(vfi.funded + _validatorCount) ); } /// @notice Internal utility to deposit a public key, its signature and 32 ETH to the consensus layer /// @param _publicKey The Public Key to deposit /// @param _signature The Signature to deposit /// @param _withdrawalCredentials The Withdrawal Credentials to deposit function _depositValidator( bytes memory _publicKey, bytes32 _pubkeyRoot, bytes memory _signature, bytes32 _withdrawalCredentials ) internal { bytes32 signatureRoot = sha256( abi.encodePacked( sha256(BytesLib.slice(_signature, 0, 64)), sha256(abi.encodePacked(BytesLib.slice(_signature, 64, SIGNATURE_LENGTH - 64), bytes32(0))) ) ); bytes32 depositDataRoot = sha256( abi.encodePacked( sha256(abi.encodePacked(_pubkeyRoot, _withdrawalCredentials)), sha256(abi.encodePacked(DEPOSIT_SIZE_AMOUNT_LITTLEENDIAN64, signatureRoot)) ) ); uint256 targetBalance = address(this).balance - DEPOSIT_SIZE; IDepositContract(StakingContractStorageLib.getDepositContract()).deposit{value: DEPOSIT_SIZE}( _publicKey, abi.encodePacked(_withdrawalCredentials), _signature, depositDataRoot ); if (address(this).balance != targetBalance) { revert DepositFailure(); } } function _depositOnOneOperator(uint256 _depositCount, uint256 _totalAvailableValidators) internal { StakingContractStorageLib.setTotalAvailableValidators(_totalAvailableValidators - _depositCount); _depositValidatorsOfOperator(0, _depositCount); } function _deposit() internal { if (StakingContractStorageLib.getDepositStopped()) { revert DepositsStopped(); } if (msg.value == 0 || msg.value % DEPOSIT_SIZE != 0) { revert InvalidDepositValue(); } uint256 totalAvailableValidators = StakingContractStorageLib.getTotalAvailableValidators(); uint256 depositCount = msg.value / DEPOSIT_SIZE; if (depositCount > totalAvailableValidators) { revert NotEnoughValidators(); } StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators(); if (operators.value.length == 0) { revert NoOperators(); } _depositOnOneOperator(depositCount, totalAvailableValidators); } function _min(uint256 _a, uint256 _b) internal pure returns (uint256) { if (_a < _b) { return _a; } return _b; } /// @notice Internal utility to compute the receiver deterministic address /// @param _publicKey Public Key assigned to the receiver /// @param _prefix Prefix used to generate multiple receivers per public key function _getDeterministicReceiver(bytes memory _publicKey, uint256 _prefix) internal view returns (address) { bytes32 publicKeyRoot = _getPubKeyRoot(_publicKey); bytes32 salt = sha256(abi.encodePacked(_prefix, publicKeyRoot)); address implementation = StakingContractStorageLib.getFeeRecipientImplementation(); return Clones.predictDeterministicAddress(implementation, salt); } /// @notice Internal utility to deploy and withdraw the fees from a receiver /// @param _publicKey Public Key assigned to the receiver /// @param _prefix Prefix used to generate multiple receivers per public key /// @param _dispatcher Address of the dispatcher contract function _deployAndWithdraw( bytes memory _publicKey, uint256 _prefix, address _dispatcher ) internal { bytes32 publicKeyRoot = _getPubKeyRoot(_publicKey); bytes32 feeRecipientSalt = sha256(abi.encodePacked(_prefix, publicKeyRoot)); address implementation = StakingContractStorageLib.getFeeRecipientImplementation(); address feeRecipientAddress = Clones.predictDeterministicAddress(implementation, feeRecipientSalt); if (feeRecipientAddress.code.length == 0) { Clones.cloneDeterministic(implementation, feeRecipientSalt); IFeeRecipient(feeRecipientAddress).init(_dispatcher, publicKeyRoot); } IFeeRecipient(feeRecipientAddress).withdraw(); } function _checkAddress(address _address) internal pure { if (_address == address(0)) { revert InvalidZeroAddress(); } } }
//SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.10; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; /// @title TUPProxy (Transparent Upgradeable Pausable Proxy) /// @author SkillZ /// @notice This contract extends the Transparent Upgradeable proxy and adds a system wide pause feature. /// When the system is paused, the fallback will fail no matter what calls are made. contract TUPProxy is TransparentUpgradeableProxy { bytes32 private constant _PAUSE_SLOT = bytes32(uint256(keccak256("eip1967.proxy.pause")) - 1); error CallWhenPaused(); constructor( address _logic, address admin_, bytes memory _data ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} /// @dev Retrieves Paused state /// @return Paused state function isPaused() external ifAdmin returns (bool) { return StorageSlot.getBooleanSlot(_PAUSE_SLOT).value; } /// @dev Pauses system function pause() external ifAdmin { StorageSlot.getBooleanSlot(_PAUSE_SLOT).value = true; } /// @dev Unpauses system function unpause() external ifAdmin { StorageSlot.getBooleanSlot(_PAUSE_SLOT).value = false; } /// @dev Overrides the fallback method to check if system is not paused before /// @dev Address Zero is allowed to perform calls even if system is paused. This allows /// view functions to be called when the system is paused as rpc providers can easily /// set the sender address to zero. function _beforeFallback() internal override { if (StorageSlot.getBooleanSlot(_PAUSE_SLOT).value == false || msg.sender == address(0)) { super._beforeFallback(); } else { revert CallWhenPaused(); } } }
{ "optimizer": { "enabled": true, "runs": 10000 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "metadata": { "useLiteralContent": true } }
[{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"getPublicKeyRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_dispatcher","type":"address"},{"internalType":"bytes32","name":"_publicKeyRoot","type":"bytes32"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
ETH | Ether (ETH) | 100.00% | $3,354.58 | 0.0386 | $129.4 |
Loading...
Loading
[ Download: CSV Export ]
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.