Transaction Hash:
Block:
22025569 at Mar-11-2025 06:59:11 PM +UTC
Transaction Fee:
0.000367641190279567 ETH
$0.70
Gas Used:
109,229 Gas / 3.365783723 Gwei
Emitted Events:
359 |
Cub.0xfc7d134b2e716a81746c1abdbababc8c42ec12a09a1ed70f07f27bdb3646e66d( 0xfc7d134b2e716a81746c1abdbababc8c42ec12a09a1ed70f07f27bdb3646e66d, 000000000000000000000000000000000000000000000000007559cbd8db19b0 )
|
360 |
Cub.0xef3abb819e31c9009302363931f93286b338da7cab0c25e6f948c7955fd5fd44( 0xef3abb819e31c9009302363931f93286b338da7cab0c25e6f948c7955fd5fd44, 0x0000000000000000000000000000c58a000000000000000003079cb3b8a2d0e9, 0x00000000000000000000000000000000000000000000000000000000000001e3, 00000000000000000000000000000000000000000000000003079cb3b8a2d0e9, 000000000000000000000000000000000000000000000000032e37f5c884bab1, 00000000000000000000000000000000000000000000000000001301da6c2efb )
|
361 |
Cub.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000051454ca2bf434808d62be235de1750dbdb113e69, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000c58a000000000000000003079cb3b8a2d0e9 )
|
362 |
Cub.0xd4f43975feb89f48dd30cabbb32011045be187d1e11c8ea9faa43efc35282519( 0xd4f43975feb89f48dd30cabbb32011045be187d1e11c8ea9faa43efc35282519, 0x00000000000000000000000051454ca2bf434808d62be235de1750dbdb113e69, 000000000000000000000000000000000000000000000000032e37f5c884bab1 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 7.376526822277038455 Eth | 7.376636051277038455 Eth | 0.000109229 | |
0x51454Ca2...BDB113e69 |
0.001006241057964072 Eth
Nonce: 42
|
0.229820759680815434 Eth
Nonce: 43
| 0.228814518622851362 | ||
0x8d6Fd650...b886943bF | 4,763.699588323437690944 Eth | 4,763.470406163624560015 Eth | 0.229182159813130929 |
Execution Trace
TUPProxy.b7ba18c7( )

Native20.multiClaim( exitQueues=[0x8d6Fd650500f82c7D978a440348e5a9b886943bF], ticketIds=[[17208079295191858097342854116139921114845417]], casksIds=[[483]] ) => ( statuses=[[0]] )
Cub.adcf1163( )
-
PluggableHatcher.status( cub=0x8d6Fd650500f82c7D978a440348e5a9b886943bF ) => ( 0xC57a4B65fc95BefB4f29E81A03fF3FEb037D3B0D, False, False )
vExitQueue.claim( ticketIds=[17208079295191858097342854116139921114845417], caskIds=[483], maxClaimDepth=65535 ) => ( statuses=[0] )
- ETH 0.229182159813130929
0x51454ca2bf434808d62be235de1750dbdb113e69.CALL( )
- ETH 0.229182159813130929
-
File 1 of 5: TUPProxy
File 2 of 5: Cub
File 3 of 5: Native20
File 4 of 5: PluggableHatcher
File 5 of 5: vExitQueue
// SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "./Freezable.sol"; /// @title Openzeppelin Transparent Upgradeable Proxy (with virtual external upgrade methods) 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) { _changeAdmin(admin_); } /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. */ // slither-disable-next-line incorrect-modifier 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 virtual 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 virtual ifAdmin { _upgradeToAndCall(newImplementation, data, true); } /** * @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(); } } /// @title TUPProxy (Transparent Upgradeable Pausable Proxy) /// @author mortimr @ Kiln /// @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, Freezable { /// @dev EIP1967 slot to store the pause status value. bytes32 private constant _PAUSE_SLOT = bytes32(uint256(keccak256("eip1967.proxy.pause")) - 1); /// @dev EIP1967 slot to store the pauser address value. bytes32 private constant _PAUSER_SLOT = bytes32(uint256(keccak256("eip1967.proxy.pauser")) - 1); /// @notice Emitted when the proxy dedicated pauser is changed. /// @param previousPauser The address of the previous pauser /// @param newPauser The address of the new pauser event PauserChanged(address previousPauser, address newPauser); /// @notice Thrown when a call was attempted and the proxy is paused. error CallWhenPaused(); // slither-disable-next-line incorrect-modifier modifier ifAdminOrPauser() { if (msg.sender == _getAdmin() || msg.sender == _getPauser()) { _; } else { _fallback(); } } /// @param _logic The address of the implementation contract /// @param admin_ The address of the admin account able to pause and upgrade the implementation /// @param _data Extra data use to perform atomic initializations constructor(address _logic, address admin_, bytes memory _data) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} /// @notice Retrieves Paused state. /// @return Paused state function paused() external ifAdminOrPauser returns (bool) { return StorageSlot.getBooleanSlot(_PAUSE_SLOT).value; } /// @notice Pauses system. function pause() external ifAdminOrPauser notFrozen { StorageSlot.getBooleanSlot(_PAUSE_SLOT).value = true; } /// @notice Unpauses system. function unpause() external ifAdmin notFrozen { StorageSlot.getBooleanSlot(_PAUSE_SLOT).value = false; } /// @notice Sets the freeze timeout. function freeze(uint256 freezeTimeout) external ifAdmin { _freeze(freezeTimeout); } /// @notice Cancels the freeze process. function cancelFreeze() external ifAdmin { _cancelFreeze(); } /// @notice Retrieve the freeze status. /// @return True if frozen function frozen() external ifAdmin returns (bool) { return _isFrozen(); } /// @notice Retrieve the freeze timestamp. /// @return The freeze timestamp function freezeTime() external ifAdmin returns (uint256) { return _freezeTime(); } /// @notice Retrieve the dedicated pauser address. /// @return The pauser address function pauser() external ifAdminOrPauser returns (address) { return _getPauser(); } /// @notice Changes the dedicated pauser address. /// @dev Not callable when frozen /// @param newPauser The new pauser address function changePauser(address newPauser) external ifAdmin notFrozen { emit PauserChanged(_getPauser(), newPauser); _changePauser(newPauser); } /// @notice Changed the proxy admin. /// @dev Not callable when frozen function changeAdmin(address newAdmin) external override ifAdmin notFrozen { _changeAdmin(newAdmin); } /// @notice Performs an upgrade without reinitialization. /// @param newImplementation The new implementation address function upgradeTo(address newImplementation) external override ifAdmin notFrozen { _upgradeToAndCall(newImplementation, bytes(""), false); } /// @notice Performs an upgrade with reinitialization. /// @param newImplementation The new implementation address /// @param data The calldata to use atomically after the implementation upgrade function upgradeToAndCall(address newImplementation, bytes calldata data) external payable override ifAdmin notFrozen { _upgradeToAndCall(newImplementation, data, true); } /// @dev Internal utility to retrieve the dedicated pauser from storage, /// @return The pauser address function _getPauser() internal view returns (address) { return StorageSlot.getAddressSlot(_PAUSER_SLOT).value; } /// @dev Internal utility to change the dedicated pauser. /// @param newPauser The new pauser address function _changePauser(address newPauser) internal { StorageSlot.getAddressSlot(_PAUSER_SLOT).value = newPauser; } /// @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. // slither-disable-next-line timestamp function _beforeFallback() internal override { if ((!StorageSlot.getBooleanSlot(_PAUSE_SLOT).value || _isFrozen()) || msg.sender == address(0)) { super._beforeFallback(); } else { revert CallWhenPaused(); } } /// @dev Internal utility to retrieve the account allowed to freeze the contract. /// @return The freezer account function _getFreezer() internal view override returns (address) { return _getAdmin(); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (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 initializing the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { _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: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; // For some unexplainable and mysterious reason, adding this line would make slither crash // This is the reason why we are not using our own unstructured storage libs in this contract // (while the libs work properly in a lot of contracts without slither having any issue with it) // import "./types/uint256.sol"; import "./libs/LibErrors.sol"; import "./libs/LibConstant.sol"; import "openzeppelin-contracts/utils/StorageSlot.sol"; /// @title Freezable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Freezable contract is used to add a freezing capability to admin related actions. /// The goal would be to ossify an implementation after a certain amount of time. // slither-disable-next-line unimplemented-functions abstract contract Freezable { /// @notice Thrown when a call happened while it was forbidden when frozen. error Frozen(); /// @notice Thrown when the provided timeout value is lower than 100 days. /// @param providedValue The user provided value /// @param minimumValue The minimum allowed value error FreezeTimeoutTooLow(uint256 providedValue, uint256 minimumValue); /// @notice Emitted when the freeze timeout is changed. /// @param freezeTime The timestamp after which the contract will be frozen event SetFreezeTime(uint256 freezeTime); /// @dev This is the keccak-256 hash of "freezable.freeze_timestamp" subtracted by 1. bytes32 private constant _FREEZE_TIMESTAMP_SLOT = 0x04b06dd5becaad633b58f99e01f1e05103eff5a573d10d18c9baf1bc4e6bfd3a; /// @dev Only callable by the freezer account. modifier onlyFreezer() { _onlyFreezer(); _; } /// @dev Only callable when not frozen. modifier notFrozen() { _notFrozen(); _; } /// @dev Override and set it to return the address to consider as the freezer. /// @return The freezer address // slither-disable-next-line dead-code function _getFreezer() internal view virtual returns (address); /// @dev Retrieve the freeze status. /// @return True if contract is frozen // slither-disable-next-line dead-code,timestamp function _isFrozen() internal view returns (bool) { uint256 freezeTime_ = _freezeTime(); return (freezeTime_ > 0 && block.timestamp >= freezeTime_); } /// @dev Retrieve the freeze timestamp. /// @return The freeze timestamp // slither-disable-next-line dead-code function _freezeTime() internal view returns (uint256) { return StorageSlot.getUint256Slot(_FREEZE_TIMESTAMP_SLOT).value; } /// @dev Internal utility to set the freeze timestamp. /// @param freezeTime The new freeze timestamp // slither-disable-next-line dead-code function _setFreezeTime(uint256 freezeTime) internal { StorageSlot.getUint256Slot(_FREEZE_TIMESTAMP_SLOT).value = freezeTime; emit SetFreezeTime(freezeTime); } /// @dev Internal utility to revert if caller is not freezer. // slither-disable-next-line dead-code function _onlyFreezer() internal view { if (msg.sender != _getFreezer()) { revert LibErrors.Unauthorized(msg.sender, _getFreezer()); } } /// @dev Internal utility to revert if contract is frozen. // slither-disable-next-line dead-code function _notFrozen() internal view { if (_isFrozen()) { revert Frozen(); } } /// @dev Internal utility to start the freezing procedure. /// @param freezeTimeout Timeout to add to current timestamp to define freeze timestamp // slither-disable-next-line dead-code function _freeze(uint256 freezeTimeout) internal { _notFrozen(); _onlyFreezer(); if (freezeTimeout < LibConstant.MINIMUM_FREEZE_TIMEOUT) { revert FreezeTimeoutTooLow(freezeTimeout, LibConstant.MINIMUM_FREEZE_TIMEOUT); } // overflow would revert uint256 now_ = block.timestamp; uint256 freezeTime_ = now_ + freezeTimeout; _setFreezeTime(freezeTime_); } /// @dev Internal utility to cancel the freezing procedure. // slither-disable-next-line dead-code function _cancelFreeze() internal { _notFrozen(); _onlyFreezer(); _setFreezeTime(0); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.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 overridden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _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 overridden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} } // 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._ */ 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: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibErrors { error Unauthorized(address account, address expected); error InvalidZeroAddress(); error InvalidNullValue(); error InvalidBPSValue(); error InvalidEmptyString(); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibConstant { /// @dev The basis points value representing 100%. uint256 internal constant BASIS_POINTS_MAX = 10_000; /// @dev The size of a deposit to activate a validator. uint256 internal constant DEPOSIT_SIZE = 32 ether; /// @dev The minimum freeze timeout before freeze is active. uint256 internal constant MINIMUM_FREEZE_TIMEOUT = 100 days; /// @dev Address used to represent ETH when an address is required to identify an asset. address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (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: * ```solidity * 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) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } } // 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 (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 (last updated v4.8.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 * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.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 functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
File 2 of 5: Cub
// SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "openzeppelin-contracts/proxy/beacon/BeaconProxy.sol"; import "./interfaces/IFixer.sol"; import "./interfaces/IHatcher.sol"; import "./interfaces/ICub.sol"; /// @title Cub /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The cub is controlled by a Hatcher in charge of providing its status details and implementation address. contract Cub is Proxy, ERC1967Upgrade, ICub { /// @notice Initializer to not rely on the constructor. /// @param beacon The address of the beacon to pull its info from /// @param data The calldata to add to the initial call, if any // slither-disable-next-line naming-convention function ___initializeCub(address beacon, bytes memory data) external { if (_getBeacon() != address(0)) { revert CubAlreadyInitialized(); } _upgradeBeaconToAndCall(beacon, data, false); } /// @dev Internal utility to retrieve the implementation from the beacon. /// @return The implementation address // slither-disable-next-line dead-code function _implementation() internal view virtual override returns (address) { return IBeacon(_getBeacon()).implementation(); } /// @dev Prevents unauthorized calls. /// @dev This will make the method transparent, forcing unauthorized callers into the fallback. modifier onlyBeacon() { if (msg.sender != _getBeacon()) { _fallback(); } else { _; } } /// @dev Prevents unauthorized calls. /// @dev This will make the method transparent, forcing unauthorized callers into the fallback. modifier onlyMe() { if (msg.sender != address(this)) { _fallback(); } else { _; } } /// @inheritdoc ICub // slither-disable-next-line reentrancy-events function appliedFixes(address[] memory fixers) public onlyMe { emit AppliedFixes(fixers); } /// @inheritdoc ICub function applyFix(address fixer) external onlyBeacon { _applyFix(fixer); } /// @dev Retrieve the list of fixes for this cub from the hatcher. /// @param beacon Address of the hatcher acting as a beacon /// @return List of fixes to apply function _fixes(address beacon) internal view returns (address[] memory) { return IHatcher(beacon).fixes(address(this)); } /// @dev Retrieve the status for this cub from the hatcher. /// @param beacon Address of the hatcher acting as a beacon /// @return First value is true if fixes are pending, second value is true if cub is paused function _status(address beacon) internal view returns (address, bool, bool) { return IHatcher(beacon).status(address(this)); } /// @dev Commits fixes to the hatcher. /// @param beacon Address of the hatcher acting as a beacon function _commit(address beacon) internal { IHatcher(beacon).commitFixes(); } /// @dev Fetches the current cub status and acts accordingly. /// @param beacon Address of the hatcher acting as a beacon function _fix(address beacon) internal returns (address) { (address implementation, bool hasFixes, bool isPaused) = _status(beacon); if (isPaused && msg.sender != address(0)) { revert CalledWhenPaused(msg.sender); } if (hasFixes) { bool isStaticCall = false; address[] memory fixes = _fixes(beacon); // This is a trick to check if the current execution context // allows state modifications try this.appliedFixes(fixes) {} catch { isStaticCall = true; } // if we properly emitted AppliedFixes, we are not in a view or pure call // we can then apply fixes if (!isStaticCall) { for (uint256 idx = 0; idx < fixes.length;) { if (fixes[idx] != address(0)) { _applyFix(fixes[idx]); } unchecked { ++idx; } } _commit(beacon); } } return implementation; } /// @dev Applies the given fix, and reverts in case of error. /// @param fixer Address that implements the fix // slither-disable-next-line controlled-delegatecall,delegatecall-loop,low-level-calls function _applyFix(address fixer) internal { (bool success, bytes memory rdata) = fixer.delegatecall(abi.encodeCall(IFixer.fix, ())); if (!success) { revert FixDelegateCallError(fixer, rdata); } (success) = abi.decode(rdata, (bool)); if (!success) { revert FixCallError(fixer); } } /// @dev Fallback method that ends up forwarding calls as delegatecalls to the implementation. function _fallback() internal override(Proxy) { _beforeFallback(); address beacon = _getBeacon(); address implementation = _fix(beacon); _delegate(implementation); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol) pragma solidity ^0.8.0; import "./IBeacon.sol"; import "../Proxy.sol"; import "../ERC1967/ERC1967Upgrade.sol"; /** * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. * * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't * conflict with the storage layout of the implementation behind the proxy. * * _Available since v3.4._ */ contract BeaconProxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the proxy with `beacon`. * * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity * constructor. * * Requirements: * * - `beacon` must be a contract with the interface {IBeacon}. */ constructor(address beacon, bytes memory data) payable { _upgradeBeaconToAndCall(beacon, data, false); } /** * @dev Returns the current beacon address. */ function _beacon() internal view virtual returns (address) { return _getBeacon(); } /** * @dev Returns the current implementation address of the associated beacon. */ function _implementation() internal view virtual override returns (address) { return IBeacon(_getBeacon()).implementation(); } /** * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}. * * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. * * Requirements: * * - `beacon` must be a contract. * - The implementation returned by `beacon` must be a contract. */ function _setBeacon(address beacon, bytes memory data) internal virtual { _upgradeBeaconToAndCall(beacon, data, false); } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Fixer /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Hatcher can deploy, upgrade, fix and pause a set of instances called cubs. /// All cubs point to the same common implementation. interface IFixer { /// @notice Interface to implement on a Fixer contract. /// @return isFixed True if fix was properly applied function fix() external returns (bool isFixed); } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "openzeppelin-contracts/proxy/beacon/IBeacon.sol"; /// @title Hatcher Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Hatcher can deploy, upgrade, fix and pause a set of instances called cubs. /// All cubs point to the same coomon implementation. interface IHatcher is IBeacon { /// @notice Emitted when the system is globally paused. event GlobalPause(); /// @notice Emitted when the system is globally unpaused. event GlobalUnpause(); /// @notice Emitted when a specific cub is paused. /// @param cub Address of the cub being paused event Pause(address cub); /// @notice Emitted when a specific cub is unpaused. /// @param cub Address of the cub being unpaused event Unpause(address cub); /// @notice Emitted when a global fix is removed. /// @param index Index of the global fix being removed event DeletedGlobalFix(uint256 index); /// @notice Emitted when a cub has properly applied a fix. /// @param cub Address of the cub that applied the fix /// @param fix Address of the fix was applied event AppliedFix(address cub, address fix); /// @notice Emitted the common implementation is updated. /// @param implementation New common implementation address event Upgraded(address indexed implementation); /// @notice Emitted a new cub is hatched. /// @param cub Address of the new instance /// @param cdata Calldata used to perform the atomic first call event Hatched(address indexed cub, bytes cdata); /// @notice Emitted a the initial progress has been changed. /// @param initialProgress New initial progress value event SetInitialProgress(uint256 initialProgress); /// @notice Emitted a new pauser is set. /// @param pauser Address of the new pauser event SetPauser(address pauser); /// @notice Emitted a cub committed some global fixes. /// @param cub Address of the cub that applied the global fixes /// @param progress New cub progress event CommittedFixes(address cub, uint256 progress); /// @notice Emitted a global fix is registered. /// @param fix Address of the new global fix /// @param index Index of the new global fix in the global fix array event RegisteredGlobalFix(address fix, uint256 index); /// @notice The provided implementation is not a smart contract. /// @param implementation The provided implementation error ImplementationNotAContract(address implementation); /// @notice Retrieve the common implementation. /// @return implementationAddress Address of the common implementation function implementation() external view returns (address implementationAddress); /// @notice Retrieve cub status details. /// @param cub The address of the cub to fetch the status of /// @return implementationAddress The current implementation address to use /// @return hasFixes True if there are fixes to apply /// @return isPaused True if the system is paused globally or the calling cub is paused function status(address cub) external view returns (address implementationAddress, bool hasFixes, bool isPaused); /// @notice Retrieve the initial progress. /// @dev This value is the starting progress value for all new cubs /// @return currentInitialProgress The initial progress function initialProgress() external view returns (uint256 currentInitialProgress); /// @notice Retrieve the current progress of a specific cub. /// @param cub Address of the cub /// @return currentProgress The current progress of the cub function progress(address cub) external view returns (uint256 currentProgress); /// @notice Retrieve the global pause status. /// @return isGlobalPaused True if globally paused function globalPaused() external view returns (bool isGlobalPaused); /// @notice Retrieve a cub pause status. /// @param cub Address of the cub /// @return isPaused True if paused function paused(address cub) external view returns (bool isPaused); /// @notice Retrieve the address of the pauser. function pauser() external view returns (address); /// @notice Retrieve a cub's global fixes that need to be applied, taking its progress into account. /// @param cub Address of the cub /// @return fixesAddresses An array of addresses that implement fixes function fixes(address cub) external view returns (address[] memory fixesAddresses); /// @notice Retrieve the raw list of global fixes. /// @return globalFixesAddresses An array of addresses that implement the global fixes function globalFixes() external view returns (address[] memory globalFixesAddresses); /// @notice Retrieve the address of the next hatched cub. /// @return nextHatchedCub The address of the next cub function nextHatch() external view returns (address nextHatchedCub); /// @notice Retrieve the freeze status. /// @return True if frozen function frozen() external view returns (bool); /// @notice Retrieve the timestamp when the freeze happens. /// @return The freeze timestamp function freezeTime() external view returns (uint256); /// @notice Creates a new cub. /// @param cdata The calldata to use for the initial atomic call /// @return cubAddress The address of the new cub function hatch(bytes calldata cdata) external returns (address cubAddress); /// @notice Creates a new cub, without calldata. /// @return cubAddress The address of the new cub function hatch() external returns (address cubAddress); /// @notice Sets the progress of the caller to the current global fixes array length. function commitFixes() external; /// @notice Sets the address of the pauser. /// @param newPauser Address of the new pauser function setPauser(address newPauser) external; /// @notice Apply a fix to several cubs. /// @param fixer Fixer contract implementing the fix /// @param cubs List of cubs to apply the fix on function applyFixToCubs(address fixer, address[] calldata cubs) external; /// @notice Apply several fixes to one cub. /// @param cub The cub to apply the fixes on /// @param fixers List of fixer contracts implementing the fixes function applyFixesToCub(address cub, address[] calldata fixers) external; /// @notice Register a new global fix for cubs to call asynchronously. /// @param fixer Address of the fixer implementing the fix function registerGlobalFix(address fixer) external; /// @notice Deletes a global fix from the array. /// @param index Index of the global fix to remove function deleteGlobalFix(uint256 index) external; /// @notice Upgrades the common implementation address. /// @param newImplementation Address of the new common implementation function upgradeTo(address newImplementation) external; /// @notice Upgrades the common implementation address and the initial progress value. /// @param newImplementation Address of the new common implementation /// @param initialProgress_ The new initial progress value function upgradeToAndChangeInitialProgress(address newImplementation, uint256 initialProgress_) external; /// @notice Sets the initial progress value. /// @param initialProgress_ The new initial progress value function setInitialProgress(uint256 initialProgress_) external; /// @notice Sets the progress of a cub. /// @param cub Address of the cub /// @param newProgress New progress value function setCubProgress(address cub, uint256 newProgress) external; /// @notice Pauses a set of cubs. /// @param cubs List of cubs to pause function pauseCubs(address[] calldata cubs) external; /// @notice Unpauses a set of cubs. /// @param cubs List of cubs to unpause function unpauseCubs(address[] calldata cubs) external; /// @notice Pauses all the cubs of the system. function globalPause() external; /// @notice Unpauses all the cubs of the system. /// @dev If a cub was specifically paused, this method won't unpause it function globalUnpause() external; /// @notice Sets the freeze timestamp. /// @param freezeTimeout The timeout to add to current timestamp before freeze happens function freeze(uint256 freezeTimeout) external; /// @notice Cancels the freezing procedure. function cancelFreeze() external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Cub /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The cub is controlled by a Hatcher in charge of providing its status details and implementation address. interface ICub { /// @notice An error occured when performing the delegatecall to the fix. /// @param fixer Address implementing the fix /// @param err The return data from the call error error FixDelegateCallError(address fixer, bytes err); /// @notice The fix method failed by returning false. /// @param fixer Added implementing the fix error FixCallError(address fixer); /// @notice A call was made while the cub was paused. /// @param caller The address that performed the call error CalledWhenPaused(address caller); error CubAlreadyInitialized(); /// @notice Emitted when several fixes have been applied. /// @param fixes List of fixes to apply event AppliedFixes(address[] fixes); /// @notice Public method that emits the AppliedFixes event. /// @dev Transparent to all callers except the cub itself /// @dev Only callable by the cub itself as a regular call /// @dev This method is used to detect the execution context (view/non-view) /// @param _fixers List of applied fixes function appliedFixes(address[] memory _fixers) external; /// @notice Applies the provided fix. /// @dev Transparent to all callers except the hatcher /// @param _fixer The address of the contract implementing the fix to apply function applyFix(address _fixer) external; } // 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 (last updated v4.6.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 overridden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _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 overridden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} } // 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._ */ 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) (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 (last updated v4.8.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 * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.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 functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (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: * ```solidity * 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) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } }
File 3 of 5: Native20
// SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/types/mapping.sol"; import "utils.sol/types/string.sol"; import "utils.sol/Implementation.sol"; import "utils.sol/Initializable.sol"; import "./MultiPool20.sol"; import "./interfaces/INative20.sol"; /// @title Native20 (V1) /// @author 0xvv @ Kiln /// @notice This contract allows users to stake any amount of ETH in the vPool(s) /// @notice Users are given non transferable ERC-20 type shares to track their stake contract Native20 is MultiPool20, INative20, Implementation, Initializable { using LMapping for types.Mapping; using LString for types.String; using LUint256 for types.Uint256; /// @dev The name of the shares. /// @dev Slot: keccak256(bytes("native20.1.name")) - 1 types.String internal constant $name = types.String.wrap(0xeee152275d096301850a53ae85c6991c818bc6bac8a2174c268aa94ed7cf06f1); /// @dev The symbol of the shares. /// @dev Slot: keccak256(bytes("native20.1.symbol")) - 1 types.String internal constant $symbol = types.String.wrap(0x4a8b3e24ebc795477af927068865c6fcc26e359a994edca2492e515a46aad711); /// @inheritdoc INative20 function initialize(Native20Configuration calldata args) external init(0) { $name.set(args.name); emit SetName(args.name); $symbol.set(args.symbol); emit SetSymbol(args.symbol); Administrable._setAdmin(args.admin); if (args.pools.length == 0) { revert EmptyPoolList(); } if (args.pools.length != args.poolFees.length) { revert UnequalLengths(args.pools.length, args.poolFees.length); } for (uint256 i = 0; i < args.pools.length;) { _addPool(args.pools[i], args.poolFees[i]); unchecked { i++; } } _setPoolPercentages(args.poolPercentages); _initFeeDispatcher(args.commissionRecipients, args.commissionDistribution); _setMaxCommission(args.maxCommissionBps); _setMonoTicketThreshold(args.monoTicketThreshold); } /// @inheritdoc INative20 function name() external view returns (string memory) { return string(abi.encodePacked($name.get())); } /// @inheritdoc INative20 function symbol() external view returns (string memory) { return string(abi.encodePacked($symbol.get())); } /// @inheritdoc INative20 function decimals() external view virtual override returns (uint8) { return 18; } /// @inheritdoc INative20 function balanceOf(address account) external view virtual returns (uint256) { return _balanceOf(account); } /// @inheritdoc INative20 function balanceOfUnderlying(address account) external view virtual returns (uint256) { return _balanceOfUnderlying(account); } /// @inheritdoc INative20 function totalSupply() external view virtual returns (uint256) { return _totalSupply(); } /// @inheritdoc INative20 function totalUnderlyingSupply() external view virtual returns (uint256) { return _totalUnderlyingSupply(); } /// @inheritdoc INative20 function stake() external payable { LibSanitize.notNullValue(msg.value); _stake(msg.value); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LMapping { // slither-disable-next-line dead-code function get(types.Mapping position) internal pure returns (mapping(uint256 => uint256) storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LString { struct StringStorage { string value; } // slither-disable-next-line dead-code function get(types.String position) internal view returns (string memory) { StringStorage storage ss; // slither-disable-next-line assembly assembly { ss.slot := position } return ss.value; } // slither-disable-next-line dead-code function set(types.String position, string memory value) internal { StringStorage storage ss; // slither-disable-next-line assembly assembly { ss.slot := position } ss.value = value; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types/uint256.sol"; /// @title Implementation /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contracts must be used on all implementation contracts. It ensures that the initializers are only callable through the proxy. /// This will brick the implementation and make it unusable directly without using delegatecalls. abstract contract Implementation { using LUint256 for types.Uint256; /// @dev The version number in storage in the initializable contract. /// @dev Slot: keccak256(bytes("initializable.version"))) - 1 types.Uint256 internal constant $initializableVersion = types.Uint256.wrap(0xc4c7f1ccb588f39a9aa57be6cfd798d73912e27b44cfa18e1a5eba7b34e81a76); constructor() { $initializableVersion.set(type(uint256).max); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types/uint256.sol"; /// @title Initializable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contracts helps upgradeable contracts handle an internal /// version value to prevent initializer replays. abstract contract Initializable { using LUint256 for types.Uint256; /// @notice The version has been initialized. /// @param version The version number initialized /// @param cdata The calldata used for the call event Initialized(uint256 version, bytes cdata); /// @notice The init modifier has already been called on the given version number. /// @param version The provided version number /// @param currentVersion The stored version number error AlreadyInitialized(uint256 version, uint256 currentVersion); /// @dev The version number in storage. /// @dev Slot: keccak256(bytes("initializable.version"))) - 1 types.Uint256 internal constant $version = types.Uint256.wrap(0xc4c7f1ccb588f39a9aa57be6cfd798d73912e27b44cfa18e1a5eba7b34e81a76); /// @dev The modifier to use on initializers. /// @dev Do not provide _version dynamically, make sure the value is hard-coded each /// time the modifier is used. /// @param _version The version to initialize // slither-disable-next-line incorrect-modifier modifier init(uint256 _version) { if (_version == $version.get()) { $version.set(_version + 1); emit Initialized(_version, msg.data); _; } else { revert AlreadyInitialized(_version, $version.get()); } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "vsuite/ctypes/ctypes.sol"; import "vsuite/ctypes/approvals_mapping.sol"; import "./MultiPool.sol"; import "./interfaces/IMultiPool20.sol"; import "./victypes/victypes.sol"; import "./victypes/balance.sol"; uint256 constant MIN_SUPPLY = 1e14; // If there is only dust in the pool, we mint 1:1 uint256 constant COMMISSION_MAX = 10; // 0.1% / 10 bps ~= 12 days of accrued commission at 3% GRR /// @title MultiPool-20 (v1) /// @author 0xvv @ Kiln /// @notice This contract contains the internal logic for an ERC-20 token based on one or multiple pools. abstract contract MultiPool20 is MultiPool, IMultiPool20 { using LArray for types.Array; using LMapping for types.Mapping; using LUint256 for types.Uint256; using LBalance for victypes.BalanceMapping; using LApprovalsMapping for ctypes.ApprovalsMapping; using CUint256 for uint256; using CBool for bool; /// @dev The total supply of ERC 20. /// @dev Slot: keccak256(bytes("multiPool20.1.totalSupply")) - 1 types.Uint256 internal constant $totalSupply = types.Uint256.wrap(0xb24a0f21470b6927dcbaaf5b1f54865bd687f4a2ce4c43edf1e20339a4c05bae); /// @dev The list containing the percentages of ETH to route to each pool, in basis points, must add up to 10 000. /// @dev Slot: keccak256(bytes("multiPool20.1.poolRoutingList")) - 1 types.Array internal constant $poolRoutingList = types.Array.wrap(0x3803482dd7707d12238e38a3b1b5e55fa6e13d81c36ce29ec5c267cc02c53fe3); /// @dev Stores the balances : mapping(address => uint256). /// @dev Slot: keccak256(bytes("multiPool20.1.balances")) - 1 victypes.BalanceMapping internal constant $balances = victypes.BalanceMapping.wrap(0x4f74125ce1aafb5d1699fc2e5e8f96929ff1a99170dc9bda82c8944acc5c7286); /// @dev Stores the approvals /// @dev Type: mapping(address => mapping(address => bool). /// @dev Slot: keccak256(bytes("multiPool20.1.approvals")) - 1 ctypes.ApprovalsMapping internal constant $approvals = ctypes.ApprovalsMapping.wrap(0xebc1e0a04bae59eb2e2b17f55cd491aec28c349ae4f6b6fe9be28a72f9c6b202); /// @dev The threshold below which we try to issue only one exit ticket /// @dev Slot: keccak256(bytes("multiPool20.1.monoTicketThreshold")) - 1 types.Uint256 internal constant $monoTicketThreshold = types.Uint256.wrap(0x900053b761278bb5de4eeaea5ed9000b89943edad45dcf64a9dab96d0ce29c2e); /// @inheritdoc IMultiPool20 function setPoolPercentages(uint256[] calldata split) external onlyAdmin { _setPoolPercentages(split); } /// @notice Sets the threshold below which we try to issue only one exit ticket /// @param minTicketEthValue The threshold function setMonoTicketThreshold(uint256 minTicketEthValue) external onlyAdmin { _setMonoTicketThreshold(minTicketEthValue); } /// @inheritdoc IMultiPool20 function requestExit(uint256 amount) external virtual { _requestExit(amount); } /// @inheritdoc IMultiPool20 function rate() external view returns (uint256) { uint256 currentTotalSupply = _totalSupply(); return currentTotalSupply > 0 ? LibUint256.mulDiv(_totalUnderlyingSupply(), 1e18, currentTotalSupply) : 1e18; } /// Private functions /// @dev Internal function to requestExit /// @param amount The amount of shares to exit // slither-disable-next-line reentrancy-events function _requestExit(uint256 amount) internal { uint256 totalSupply = $totalSupply.get(); uint256 totalUnderlyingSupply = _totalUnderlyingSupply(); _burn(msg.sender, amount); uint256 ethValue = LibUint256.mulDiv(amount, totalUnderlyingSupply, totalSupply); uint256 poolCount_ = $poolCount.get(); // Early return in case of mono pool operation if (poolCount_ == 1) { PoolExitDetails[] memory detail = new PoolExitDetails[](1); _sendToExitQueue(0, ethValue, detail[0]); _checkCommissionRatio(0); emit Exit(msg.sender, uint128(amount), detail); return; } uint256[] memory splits = $poolRoutingList.toUintA(); // If the amount is below the set threshold we exit via the most imabalanced pool to print only 1 ticket if (ethValue < $monoTicketThreshold.get()) { int256 maxImbalance = 0; uint256 exitPoolId = 0; for (uint256 id = 0; id < poolCount_;) { uint256 expectedValue = LibUint256.mulDiv(totalUnderlyingSupply, splits[id], LibConstant.BASIS_POINTS_MAX); uint256 poolValue = _ethAfterCommission(id); int256 imbalance = int256(poolValue) - int256(expectedValue); if (poolValue >= ethValue && imbalance > maxImbalance) { maxImbalance = imbalance; exitPoolId = id; } unchecked { id++; } } if (maxImbalance > 0) { PoolExitDetails[] memory detail = new PoolExitDetails[](1); _sendToExitQueue(exitPoolId, ethValue, detail[0]); _checkCommissionRatio(exitPoolId); emit Exit(msg.sender, uint128(amount), detail); return; } } // If the the amount is over the threshold or no pool has enough value to cover the exit // We exit proportionally to maintain the balance PoolExitDetails[] memory details = new PoolExitDetails[](poolCount_); for (uint256 id = 0; id < poolCount_;) { uint256 ethForPool = LibUint256.mulDiv(ethValue, splits[id], LibConstant.BASIS_POINTS_MAX); if (ethForPool > 0) _sendToExitQueue(id, ethForPool, details[id]); _checkCommissionRatio(id); unchecked { id++; } } emit Exit(msg.sender, uint128(amount), details); } /// @dev Internal function to exit the commission shares if needed /// @param id The pool id function _checkCommissionRatio(uint256 id) internal { // If the commission shares / all shares ratio go over the limit we exit them if (_poolSharesOfIntegrator(id) > LibUint256.mulDiv($poolShares.get()[id], COMMISSION_MAX, LibConstant.BASIS_POINTS_MAX)) { _exitCommissionShares(id); } } /// @dev Utility function to send a given ETH amount of shares to the exit queue of a pool // slither-disable-next-line calls-loop function _sendToExitQueue(uint256 poolId, uint256 ethAmount, PoolExitDetails memory details) internal { IvPool pool = _getPool(poolId); uint256 shares = LibUint256.mulDiv(ethAmount, pool.totalSupply(), pool.totalUnderlyingSupply()); uint256 stakedValueBefore = _stakedEthValue(poolId); details.exitedPoolShares = uint128(shares); details.poolId = uint128(poolId); _sendSharesToExitQueue(poolId, shares, pool, msg.sender); $exitedEth.get()[poolId] += stakedValueBefore - _stakedEthValue(poolId); } /// @dev Internal function to stake in one or more pools with arbitrary amounts to each one /// @param totalAmount The amount of ETH to stake // slither-disable-next-line reentrancy-events,unused-return,dead-code function _stake(uint256 totalAmount) internal notPaused returns (bool) { uint256[] memory splits = $poolRoutingList.toUintA(); PoolStakeDetails[] memory stakeDetails = new PoolStakeDetails[](splits.length); uint256 tokensBoughtTotal = 0; for (uint256 id = 0; id < $poolCount.get();) { if (splits[id] > 0) { stakeDetails[id].poolId = uint128(id); uint256 remainingEth = LibUint256.mulDiv(totalAmount, splits[id], LibConstant.BASIS_POINTS_MAX); _checkPoolIsEnabled(id); IvPool pool = _getPool(id); uint256 totalSupply = _totalSupply(); // we can use these values because the ratio of shares to underlying is constant in this function uint256 totalUnderlyingSupply = _totalUnderlyingSupply(); if (totalSupply < MIN_SUPPLY) { $injectedEth.get()[id] += remainingEth; uint256 sharesAcquired = pool.deposit{value: remainingEth}(); tokensBoughtTotal += sharesAcquired; _mint(msg.sender, sharesAcquired); stakeDetails[id].ethToPool = uint128(remainingEth); stakeDetails[id].pSharesFromPool = uint128(sharesAcquired); } else { uint256 comOwed = _integratorCommissionOwed(id); uint256 tokensBoughtPool = 0; // If there is enough commission we sell it first // This avoids wasting gas to sell infinitesimal amounts of commission + a potential DoS vector if (comOwed > MIN_COMMISSION_TO_SELL) { uint256 ethForCommission = LibUint256.min(comOwed, remainingEth); remainingEth -= ethForCommission; uint256 pSharesBought = LibUint256.mulDiv( ethForCommission, $poolShares.get()[id] - _poolSharesOfIntegrator(id), _ethAfterCommission(id) ); $commissionPaid.get()[id] += ethForCommission; stakeDetails[id].ethToIntegrator = uint128(ethForCommission); stakeDetails[id].pSharesFromIntegrator = uint128(pSharesBought); emit CommissionSharesSold(pSharesBought, id, ethForCommission); uint256 tokensAcquired = LibUint256.mulDiv(ethForCommission, totalSupply, totalUnderlyingSupply); if (tokensAcquired == 0) revert ZeroSharesMint(); tokensBoughtPool += tokensAcquired; } if (remainingEth > 0) { $injectedEth.get()[id] += remainingEth; uint256 pShares = pool.deposit{value: remainingEth}(); uint256 tokensAcquired = LibUint256.mulDiv(remainingEth, totalSupply, totalUnderlyingSupply); if (tokensAcquired == 0) revert ZeroSharesMint(); stakeDetails[id].ethToPool += uint128(remainingEth); stakeDetails[id].pSharesFromPool += uint128(pShares); tokensBoughtPool += tokensAcquired; } _mint(msg.sender, tokensBoughtPool); tokensBoughtTotal += tokensBoughtPool; } } unchecked { id++; } } emit Stake(msg.sender, uint128(totalAmount), uint128(tokensBoughtTotal), stakeDetails); return true; } /// @dev Internal function to set the pool percentages /// @param percentages The new percentages function _setPoolPercentages(uint256[] calldata percentages) internal { if (percentages.length != $poolCount.get()) { revert UnequalLengths(percentages.length, $poolCount.get()); } uint256 total = 0; $poolRoutingList.del(); uint256[] storage percentagesList = $poolRoutingList.toUintA(); for (uint256 i = 0; i < percentages.length;) { bool enabled = $poolActivation.get()[i].toBool(); uint256 percentage = percentages[i]; if (!enabled && percentage != 0) { revert NonZeroPercentageOnDeactivatedPool(i); } else { total += percentages[i]; percentagesList.push(percentages[i]); } unchecked { i++; } } if (total != LibConstant.BASIS_POINTS_MAX) { revert LibErrors.InvalidBPSValue(); } emit SetPoolPercentages(percentages); } /// @inheritdoc IMultiPool20 function setPoolActivation(uint256 poolId, bool status, uint256[] calldata newPoolPercentages) external onlyAdmin { $poolActivation.get()[poolId] = status.v(); _setPoolPercentages(newPoolPercentages); } /// @dev Internal function to retrieve the balance of a given account /// @param account The account to retrieve the balance of // slither-disable-next-line dead-code function _balanceOf(address account) internal view returns (uint256) { return $balances.get()[account]; } /// @dev Internal function to retrieve the balance of a given account in underlying /// @param account The account to retrieve the balance of in underlying // slither-disable-next-line dead-code function _balanceOfUnderlying(address account) internal view returns (uint256) { uint256 tUnderlyingSupply = _totalUnderlyingSupply(); uint256 tSupply = _totalSupply(); if (tUnderlyingSupply == 0 || tSupply == 0) { return 0; } return LibUint256.mulDiv($balances.get()[account], tUnderlyingSupply, tSupply); } /// @dev Internal function retrieve the total underlying supply // slither-disable-next-line naming-convention function _totalUnderlyingSupply() internal view returns (uint256) { uint256 ethValue = 0; for (uint256 i = 0; i < $poolCount.get();) { unchecked { ethValue += _ethAfterCommission(i); i++; } } return ethValue; } /// @dev Internal function to retrieve the total supply // slither-disable-next-line naming-convention function _totalSupply() internal view returns (uint256) { return $totalSupply.get(); } /// @dev Internal function to transfer tokens from one account to another /// @param from The account to transfer from /// @param to The account to transfer to /// @param amount The amount to transfer // slither-disable-next-line dead-code function _transfer(address from, address to, uint256 amount) internal virtual { uint256 fromBalance = $balances.get()[from]; if (amount > fromBalance) { revert InsufficientBalance(amount, fromBalance); } unchecked { $balances.get()[from] = fromBalance - amount; } $balances.get()[to] += amount; emit Transfer(from, to, amount); } /// @dev Internal function to retrieve the allowance of a given spender /// @param owner The owner of the allowance /// @param spender The spender of the allowance // slither-disable-next-line dead-code function _allowance(address owner, address spender) internal view returns (uint256) { return $approvals.get()[owner][spender]; } /// @dev Internal function to approve a spender /// @param owner The owner of the allowance /// @param spender The spender of the allowance /// @param amount The amount to approve // slither-disable-next-line dead-code function _approve(address owner, address spender, uint256 amount) internal { $approvals.get()[owner][spender] = amount; emit Approval(owner, spender, amount); } /// @dev Internal function to transfer tokens from one account to another /// @param spender The spender of the allowance /// @param from The account to transfer from /// @param to The account to transfer to /// @param amount The amount to transfer // slither-disable-next-line dead-code function _transferFrom(address spender, address from, address to, uint256 amount) internal virtual { uint256 currentAllowance = $approvals.get()[from][spender]; if (amount > currentAllowance) { revert InsufficientAllowance(amount, currentAllowance); } unchecked { $approvals.get()[from][spender] = currentAllowance - amount; } _transfer(from, to, amount); } /// @dev Internal function for minting /// @param account The address to mint to /// @param amount The amount to mint // slither-disable-next-line dead-code function _mint(address account, uint256 amount) internal { $totalSupply.set($totalSupply.get() + amount); unchecked { // Overflow not possible: balance + amount is at most totalSupply + amount, checked above $balances.get()[account] += amount; } emit Transfer(address(0), account, amount); } /// @dev Internal function to burn tokens /// @param account The account to burn from /// @param amount The amount to burn // slither-disable-next-line dead-code function _burn(address account, uint256 amount) internal { uint256 accountBalance = $balances.get()[account]; if (amount > accountBalance) { revert InsufficientBalance(amount, accountBalance); } $totalSupply.set($totalSupply.get() - amount); unchecked { $balances.get()[account] = accountBalance - amount; } emit Transfer(account, address(0), amount); } /// @dev Internal function to set the mono ticket threshold /// @param minTicketEthValue The minimum ticket value function _setMonoTicketThreshold(uint256 minTicketEthValue) internal { $monoTicketThreshold.set(minTicketEthValue); } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; /// @notice Configuration parameters for the Native20 contract. /// @param admin The address of the admin. /// @param name ERC-20 style display name. /// @param symbol ERC-20 style display symbol. /// @param pools List of pool addresses. /// @param poolFees List of fee for each pool, in basis points. /// @param commissionRecipients List of recipients among which the withdrawn fees are shared. /// @param commissionDistribution Share of each fee recipient, in basis points, must add up to 10 000. /// @param poolPercentages The amount of ETH to route to each pool when staking, in basis points, must add up to 10 000. struct Native20Configuration { string name; string symbol; address admin; address[] pools; uint256[] poolFees; address[] commissionRecipients; uint256[] commissionDistribution; uint256[] poolPercentages; uint256 maxCommissionBps; uint256 monoTicketThreshold; } /// @title Native20 (V1) Interface /// @author 0xvv @ Kiln /// @notice This contract allows users to stake any amount of ETH in the vPool(s). /// Users are given non transferable ERC-20 type shares to track their stake. interface INative20 { /// @notice Initializes the contract with the given parameters. /// @param args The initialization arguments. function initialize(Native20Configuration calldata args) external; /// @notice Returns the name of the token. function name() external view returns (string memory); /// @notice Returns the symbol of the token, usually a shorter version of the name. function symbol() external view returns (string memory); /// @notice Returns the number of decimals used to get its user representation. function decimals() external view returns (uint8); /// @notice Returns the total amount of staking shares. /// @return Total amount of shares. function totalSupply() external view returns (uint256); /// @notice Returns the amount of ETH owned by the users in the pool(s). /// @return Total amount of shares. function totalUnderlyingSupply() external view returns (uint256); /// @notice Returns the amount of staking shares for an account. /// @param account The address of the account. /// @return amount of staking shares. function balanceOf(address account) external view returns (uint256); /// @notice Returns the ETH value of the account balance. /// @param account The address of the account. /// @return amount of ETH. function balanceOfUnderlying(address account) external view returns (uint256); /// @notice Function to stake ETH. function stake() external payable; } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @dev Library holding bytes32 custom types // slither-disable-next-line naming-convention library types { type Uint256 is bytes32; type Address is bytes32; type Bytes32 is bytes32; type Bool is bytes32; type String is bytes32; type Mapping is bytes32; type Array is bytes32; } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LUint256 { // slither-disable-next-line dead-code function get(types.Uint256 position) internal view returns (uint256 data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Uint256 position, uint256 data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Uint256 position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CUint256 { // slither-disable-next-line dead-code function toBytes32(uint256 val) internal pure returns (bytes32) { return bytes32(val); } // slither-disable-next-line dead-code function toAddress(uint256 val) internal pure returns (address) { return address(uint160(val)); } // slither-disable-next-line dead-code function toBool(uint256 val) internal pure returns (bool) { return (val & 1) == 1; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/libs/LibPublicKey.sol"; import "utils.sol/libs/LibSignature.sol"; /// @title Custom Types // slither-disable-next-line naming-convention library ctypes { /// @notice Structure representing a validator in the factory /// @param publicKey The public key of the validator /// @param signature The signature used for the deposit /// @param feeRecipient The address receiving the exec layer fees struct Validator { LibPublicKey.PublicKey publicKey; LibSignature.Signature signature; address feeRecipient; } /// @notice Structure representing a withdrawal channel in the factory /// @param validators The validators in the channel /// @param lastEdit The last time the channel was edited (in blocks) /// @param limit The staking limit of the channel. Always <= validators.length /// @param funded The amount of funded validators in the channel struct WithdrawalChannel { Validator[] validators; uint256 lastEdit; uint32 limit; uint32 funded; } /// @notice Structure representing a deposit in the factory /// @param index The index of the deposit in the withdrawal channel /// @param withdrawalChannel The withdrawal channel of the validator /// @param owner The owner of the deposited validator struct Deposit { uint256 index; bytes32 withdrawalChannel; address owner; } /// @notice Structure representing the operator metadata in the factory /// @param name The name of the operator /// @param url The url of the operator /// @param iconUrl The icon url of the operator struct Metadata { string name; string url; string iconUrl; } /// @notice Structure representing the global consensus layer spec held in the global consensus layer spec holder /// @param genesisTimestamp The timestamp of the genesis of the consensus layer (slot 0 timestamp) /// @param epochsUntilFinal The number of epochs until a block is considered final by the vsuite /// @param slotsPerEpoch The number of slots per epoch (32 on mainnet) /// @param secondsPerSlot The number of seconds per slot (12 on mainnet) struct ConsensusLayerSpec { uint64 genesisTimestamp; uint64 epochsUntilFinal; uint64 slotsPerEpoch; uint64 secondsPerSlot; } /// @notice Structure representing the report bounds held in the pools /// @param maxAPRUpperBound The maximum APR upper bound, representing the maximum increase in underlying balance checked at each oracle report /// @param maxAPRUpperCoverageBoost The maximum APR upper coverage boost, representing the additional increase allowed when pulling coverage funds /// @param maxRelativeLowerBound The maximum relative lower bound, representing the maximum decrease in underlying balance checked at each oracle report struct ReportBounds { uint64 maxAPRUpperBound; uint64 maxAPRUpperCoverageBoost; uint64 maxRelativeLowerBound; } /// @notice Structure representing the consensus layer report submitted by oracle members /// @param balanceSum sum of all the balances of all validators that have been activated by the vPool /// this means that as long as the validator was activated, no matter its current status, its balance is taken /// into account /// @param exitedSum sum of all the ether that has been exited by the validators that have been activated by the vPool /// to compute this value, we look for withdrawal events inside the block bodies that have happened at an epoch /// that is greater or equal to the withdrawable epoch of a validator purchased by the pool /// when we detect any, we take min(amount,32 eth) into account as exited balance /// @param skimmedSum sum of all the ether that has been skimmed by the validators that have been activated by the vPool /// similar to the exitedSum, we look for withdrawal events. If the epochs is lower than the withdrawable epoch /// we take into account the full withdrawal amount, otherwise we take amount - min(amount, 32 eth) into account /// @param slashedSum sum of all the ether that has been slashed by the validators that have been activated by the vPool /// to compute this value, we look for validators that are of have been in the slashed state /// then we take the balance of the validator at the epoch prior to its slashing event /// we then add the delta between this old balance and the current balance (or balance just before withdrawal) /// @param exiting amount of currently exiting eth, that will soon hit the withdrawal recipient /// this value is computed by taking the balance of any validator in the exit or slashed state or after /// @param maxExitable maximum amount that can get requested for exits during report processing /// this value is determined by the oracle. its calculation logic can be updated but all members need to agree and reach /// consensus on the new calculation logic. Its role is to control the rate at which exit requests are performed /// @param maxCommittable maximum amount that can get committed for deposits during report processing /// positive value means commit happens before possible exit boosts, negative after /// similar to the mexExitable, this value is determined by the oracle. its calculation logic can be updated but all /// members need to agree and reach consensus on the new calculation logic. Its role is to control the rate at which /// deposit are made. Committed funds are funds that are always a multiple of 32 eth and that cannot be used for /// anything else than purchasing validator, as opposed to the deposited funds that can still be used to fuel the /// exit queue in some cases. /// @param epoch epoch at which the report was crafter /// @param activatedCount current count of validators that have been activated by the vPool /// no matter the current state of the validator, if it has been activated, it has to be accounted inside this value /// @param stoppedCount current count of validators that have been stopped (being in the exit queue, exited or slashed) struct ValidatorsReport { uint128 balanceSum; uint128 exitedSum; uint128 skimmedSum; uint128 slashedSum; uint128 exiting; uint128 maxExitable; int256 maxCommittable; uint64 epoch; uint32 activatedCount; uint32 stoppedCount; } /// @notice Structure representing the ethers held in the pools /// @param deposited The amount of deposited ethers, that can either be used to boost exits or get committed /// @param committed The amount of committed ethers, that can only be used to purchase validators struct Ethers { uint128 deposited; uint128 committed; } /// @notice Structure representing a ticket in the exit queue /// @param position The position of the ticket in the exit queue (equal to the position + size of the previous ticket) /// @param size The size of the ticket in the exit queue (in pool shares) /// @param maxExitable The maximum amount of ethers that can be exited by the ticket owner (no more rewards in the exit queue, losses are still mutualized) struct Ticket { uint128 position; uint128 size; uint128 maxExitable; } /// @notice Structure representing a cask in the exit queue. This entity is created by the pool upon oracle reports, when exit liquidity is available to feed the exit queue /// @param position The position of the cask in the exit queue (equal to the position + size of the previous cask) /// @param size The size of the cask in the exit queue (in pool shares) /// @param value The value of the cask in the exit queue (in ethers) struct Cask { uint128 position; uint128 size; uint128 value; } type DepositMapping is bytes32; type WithdrawalChannelMapping is bytes32; type BalanceMapping is bytes32; type MetadataStruct is bytes32; type ConsensusLayerSpecStruct is bytes32; type ReportBoundsStruct is bytes32; type ApprovalsMapping is bytes32; type ValidatorsReportStruct is bytes32; type EthersStruct is bytes32; type TicketArray is bytes32; type CaskArray is bytes32; type FactoryDepositorMapping is bytes32; } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "./ctypes.sol"; /// @title Approval Mapping Custom Type library LApprovalsMapping { function get(ctypes.ApprovalsMapping position) internal pure returns (mapping(address => mapping(address => uint256)) storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/Administrable.sol"; import "utils.sol/types/mapping.sol"; import "utils.sol/types/uint256.sol"; import "utils.sol/types/bool.sol"; import "utils.sol/types/address.sol"; import "vsuite/interfaces/IvPool.sol"; import "./interfaces/IMultiPool.sol"; import "./FeeDispatcher.sol"; import "./ExitQueueClaimHelper.sol"; uint256 constant MIN_COMMISSION_TO_SELL = 1e9; // If there is less than a gwei of commission to sell, we don't sell it /// @title MultiPool (v1) /// @author 0xvv @ Kiln /// @notice This contract contains the common functions to all integration contracts /// @notice Contains the functions to add pools, activate/deactivate a pool, change the fee of a pool and change the commission distribution abstract contract MultiPool is IMultiPool, FeeDispatcher, Administrable, ExitQueueClaimHelper { using LArray for types.Array; using LMapping for types.Mapping; using LUint256 for types.Uint256; using LBool for types.Bool; using CAddress for address; using CBool for bool; using CUint256 for uint256; /// @dev The mapping of pool addresses /// @dev Type: mapping(uint256 => address) /// @dev Slot: keccak256(bytes("multiPool.1.poolMap")) - 1 types.Mapping internal constant $poolMap = types.Mapping.wrap(0xbbbff6eb43d00812703825948233d51219dc930ada33999d17cf576c509bebe5); /// @dev The mapping of fee amounts in basis point to be applied on rewards from different pools /// @dev Type: mapping(uint256 => uint256) /// @dev Slot: keccak256(bytes("multiPool.1.fees")) - 1 types.Mapping internal constant $fees = types.Mapping.wrap(0x725bc5812d869f51ca713008babaeead3e54db7feab7d4cb185136396950f0e3); /// @dev The mapping of commission paid for different pools /// @dev Type: mapping(uint256 => uint256) /// @dev Slot: keccak256(bytes("multiPool.1.commissionPaid")) - 1 types.Mapping internal constant $commissionPaid = types.Mapping.wrap(0x6c8f9259db4f6802ea7a1e0a01ddb54668b622f1e8d6b610ad7ba4d95f59da29); /// @dev The mapping of injected Eth for different pools /// @dev Type: mapping(uint256 => uint256) /// @dev Slot: keccak256(bytes("multiPool.1.injectedEth")) - 1 types.Mapping internal constant $injectedEth = types.Mapping.wrap(0x03abd4c14227eca60c6fecceef3797455c352f43ab35128096ea0ac0d9b2170a); /// @dev The mapping of exited Eth for different pools /// @dev Type: mapping(uint256 => uint256) /// @dev Slot: keccak256(bytes("multiPool.1.exitedEth")) - 1 types.Mapping internal constant $exitedEth = types.Mapping.wrap(0x76a0ecda094c6ccf2a55f6f1ef41b98d3c1f2dfcb9c1970701fe842ce778ff9b); /// @dev The mapping storing whether users can deposit or not to each pool /// @dev Type: mapping(uint256 => bool) /// @dev Slot: keccak256(bytes("multiPool.1.poolActivation")) - 1 types.Mapping internal constant $poolActivation = types.Mapping.wrap(0x17b1774c0811229612ec3762023ccd209d6a131e52cdd22f3427eaa8005bcb2f); /// @dev The mapping of pool shares owned for each pools /// @dev Type: mapping(uint256 => uint256) /// @dev Slot: keccak256(bytes("multiPool.1.poolShares")) - 1 types.Mapping internal constant $poolShares = types.Mapping.wrap(0x357e26a850dc4edaa8b82b6511eec141075372c9c551d3ddb37c35a301f00018); /// @dev The number of pools. /// @dev Slot: keccak256(bytes("multiPool.1.poolCount")) - 1 types.Uint256 internal constant $poolCount = types.Uint256.wrap(0xce6dbdcc28927f6ed428550e539c70c9145bd20fc6e3d7611bd20e170e9b1840); /// @dev True if deposits are paused /// @dev Slot: keccak256(bytes("multiPool.1.depositsPaused")) - 1 types.Bool internal constant $depositPaused = types.Bool.wrap(0xa030c45ae387079bc9a34aa1365121b47b8ef2d06c04682ce63b90b7c06843e7); /// @dev The maximum commission that can be set for a pool, in basis points, to be set at initialization /// @dev Slot: keccak256(bytes("multiPool.1.maxCommission")) - 1 types.Uint256 internal constant $maxCommission = types.Uint256.wrap(0x70be78e680b682a5a3c38e305d79e28594fd0c62048cca29ef1bd1d746ca8785); /// @notice This modifier reverts if the deposit is paused modifier notPaused() { if ($depositPaused.get()) { revert DepositsPaused(); } _; } /// @inheritdoc IMultiPool function pools() public view returns (address[] memory) { uint256 length = $poolCount.get(); address[] memory poolAddresses = new address[](length); for (uint256 i = 0; i < length;) { poolAddresses[i] = $poolMap.get()[i].toAddress(); unchecked { i++; } } return poolAddresses; } /// @inheritdoc IMultiPool function pauseDeposits(bool isPaused) external onlyAdmin { emit SetDepositsPaused(isPaused); $depositPaused.set(isPaused); } /// @inheritdoc IMultiPool function depositsPaused() external view returns (bool) { return $depositPaused.get(); } /// @inheritdoc IMultiPool function getFee(uint256 poolId) public view returns (uint256) { return $fees.get()[poolId]; } /// @inheritdoc IMultiPool // slither-disable-next-line reentrancy-events function changeFee(uint256 poolId, uint256 newFeeBps) external onlyAdmin { uint256 earnedBeforeFeeUpdate = _integratorCommissionEarned(poolId); _setFee(newFeeBps, poolId); uint256 earnedAfterFeeUpdate = _integratorCommissionEarned(poolId); uint256 paid = $commissionPaid.get()[poolId]; uint256 paidAndEarnedAfter = paid + earnedAfterFeeUpdate; if (paidAndEarnedAfter < earnedBeforeFeeUpdate) { revert CommissionPaidUnderflow(); } $commissionPaid.get()[poolId] = paidAndEarnedAfter - earnedBeforeFeeUpdate; } /// @inheritdoc IMultiPool function changeSplit(address[] calldata recipients, uint256[] calldata splits) external onlyAdmin { _setFeeSplit(recipients, splits); } /// @inheritdoc IMultiPool function addPool(address pool, uint256 feeBps) external onlyAdmin { _addPool(pool, feeBps); } /// @inheritdoc IMultiPool function getPoolActivation(uint256 poolId) external view returns (bool) { return $poolActivation.get()[poolId].toBool(); } /// @inheritdoc IMultiPool function integratorCommissionOwed(uint256 poolId) external view returns (uint256) { return _integratorCommissionOwed(poolId); } /// @inheritdoc IMultiPool function exitCommissionShares(uint256 poolId) external onlyAdmin { _exitCommissionShares(poolId); } /// @inheritdoc IvPoolSharesReceiver function onvPoolSharesReceived(address operator, address from, uint256 amount, bytes memory) external returns (bytes4) { uint256 poolId = _findPoolIdOrRevert(msg.sender); if (!$poolActivation.get()[poolId].toBool()) revert PoolDisabled(poolId); // Check this callback is from minting, we can only receive shares from the pool when depositing if ($poolMap.get()[poolId].toAddress() != operator || from != address(0)) { revert CallbackNotFromMinting(); } $poolShares.get()[poolId] += amount; emit VPoolSharesReceived(msg.sender, poolId, amount); return IvPoolSharesReceiver.onvPoolSharesReceived.selector; } /// PRIVATE METHODS /// @dev Internal utility to exit commission shares /// @param poolId The vPool id // slither-disable-next-line reentrancy-events function _exitCommissionShares(uint256 poolId) internal { if (poolId >= $poolCount.get()) revert InvalidPoolId(poolId); uint256 shares = _poolSharesOfIntegrator(poolId); if (shares == 0) revert NoSharesToExit(poolId); address[] memory recipients = $feeRecipients.toAddressA(); uint256[] memory weights = $feeSplits.toUintA(); IvPool pool = _getPool(poolId); for (uint256 i = 0; i < recipients.length;) { uint256 share = LibUint256.mulDiv(shares, weights[i], LibConstant.BASIS_POINTS_MAX); if (share > 0) { _sendSharesToExitQueue(poolId, share, pool, recipients[i]); } unchecked { ++i; } } $exitedEth.get()[poolId] += LibUint256.mulDiv(shares, pool.totalUnderlyingSupply(), pool.totalSupply()); $commissionPaid.get()[poolId] = _integratorCommissionEarned(poolId); emit ExitedCommissionShares(poolId, shares, weights, recipients); } /// @dev Internal utility to send pool shares to the exit queue // slither-disable-next-line calls-loop function _sendSharesToExitQueue(uint256 poolId, uint256 shares, IvPool pool, address ticketOwner) internal { $poolShares.get()[poolId] -= shares; bool result = pool.transferShares(pool.exitQueue(), shares, abi.encodePacked(ticketOwner)); if (!result) { revert PoolTransferFailed(poolId); } } /// @notice Internal utility to find the id of a pool using its address /// @dev Reverts if the address is not found /// @param poolAddress address of the pool to look up function _findPoolIdOrRevert(address poolAddress) internal view returns (uint256) { for (uint256 id = 0; id < $poolCount.get();) { if (poolAddress == $poolMap.get()[id].toAddress()) { return id; } unchecked { id++; } } revert NotARegisteredPool(poolAddress); } /// @dev Internal utility to set the integrator fee value /// @param integratorFeeBps The new integrator fee in bps /// @param poolId The vPool id function _setFee(uint256 integratorFeeBps, uint256 poolId) internal { if (integratorFeeBps > $maxCommission.get()) { revert FeeOverMax($maxCommission.get()); } $fees.get()[poolId] = integratorFeeBps; emit SetFee(poolId, integratorFeeBps); } /// @dev Internal utility to get get the pool address /// @param poolId The index of the pool /// @return The pool // slither-disable-next-line naming-convention function _getPool(uint256 poolId) public view returns (IvPool) { if (poolId >= $poolCount.get()) { revert InvalidPoolId(poolId); } return IvPool($poolMap.get()[poolId].toAddress()); } /// @dev Add a pool to the list. /// @param newPool new pool address. /// @param fee fees in basis points of ETH. // slither-disable-next-line dead-code function _addPool(address newPool, uint256 fee) internal { LibSanitize.notInvalidBps(fee); LibSanitize.notZeroAddress(newPool); uint256 poolId = $poolCount.get(); for (uint256 i = 0; i < poolId;) { if (newPool == $poolMap.get()[i].toAddress()) { revert PoolAlreadyRegistered(newPool); } unchecked { i++; } } $poolMap.get()[poolId] = newPool.v(); $fees.get()[poolId] = fee; $poolActivation.get()[poolId] = true.v(); $poolCount.set(poolId + 1); emit PoolAdded(newPool, poolId); emit SetFee(poolId, fee); } /// @dev Reverts if the given pool is not enabled. /// @param poolId pool id. // slither-disable-next-line dead-code function _checkPoolIsEnabled(uint256 poolId) internal view { if (poolId >= $poolCount.get()) { revert InvalidPoolId(poolId); } bool status = $poolActivation.get()[poolId].toBool(); if (!status) { revert PoolDisabled(poolId); } } /// @dev Returns the ETH value of the vPool shares in the contract. /// @return amount of ETH. // slither-disable-next-line calls-loop function _stakedEthValue(uint256 poolId) internal view returns (uint256) { IvPool pool = _getPool(poolId); uint256 poolTotalSupply = pool.totalSupply(); if (poolTotalSupply == 0) { return 0; } return LibUint256.mulDiv($poolShares.get()[poolId], pool.totalUnderlyingSupply(), poolTotalSupply); } /// @dev Returns the amount of ETH earned by the integrator. /// @return amount of ETH. function _integratorCommissionEarned(uint256 poolId) internal view returns (uint256) { uint256 staked = _stakedEthValue(poolId); uint256 injected = $injectedEth.get()[poolId]; uint256 exited = $exitedEth.get()[poolId]; if (injected >= staked + exited) { // Can happen right after staking due to rounding error return 0; } uint256 rewardsEarned = staked + exited - injected; return LibUint256.mulDiv(rewardsEarned, $fees.get()[poolId], LibConstant.BASIS_POINTS_MAX); } /// @dev Returns the amount of ETH owed to the integrator. /// @return amount of ETH. // slither-disable-next-line dead-code function _integratorCommissionOwed(uint256 poolId) internal view returns (uint256) { uint256 earned = _integratorCommissionEarned(poolId); uint256 paid = $commissionPaid.get()[poolId]; if (earned > paid) { return earned - paid; } else { return 0; } } /// @dev Returns the ETH value of the vPool shares after subtracting integrator commission. /// @return amount of ETH. // slither-disable-next-line dead-code function _ethAfterCommission(uint256 poolId) internal view returns (uint256) { return _stakedEthValue(poolId) - _integratorCommissionOwed(poolId); } /// @dev Returns the number of vPool shares owed as commission. /// @return amount of shares. // slither-disable-next-line calls-loop,dead-code function _poolSharesOfIntegrator(uint256 poolId) internal view returns (uint256) { IvPool pool = IvPool($poolMap.get()[poolId].toAddress()); uint256 poolTotalUnderlying = pool.totalUnderlyingSupply(); return poolTotalUnderlying == 0 ? 0 : LibUint256.mulDiv(_integratorCommissionOwed(poolId), pool.totalSupply(), poolTotalUnderlying); } /// @dev Internal utility to set the max commission value /// @param maxCommission The new max commission in bps // slither-disable-next-line dead-code function _setMaxCommission(uint256 maxCommission) internal { LibSanitize.notInvalidBps(maxCommission); $maxCommission.set(maxCommission); emit SetMaxCommission(maxCommission); } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; /// @title MultiPool-20 (V1) Interface /// @author 0xvv @ Kiln /// @notice This contract contains the internal logic for an ERC-20 token based on one or multiple pools. interface IMultiPool20 { /// @notice Emitted when a stake is transferred. /// @param from The address sending the stake /// @param to The address receiving the stake /// @param value The transfer amount event Transfer(address indexed from, address indexed to, uint256 value); /// @notice Emitted when an allowance is created. /// @param owner The owner of the shares /// @param spender The address that can spend /// @param value The allowance amount event Approval(address indexed owner, address indexed spender, uint256 value); /// @notice Emitted when some integrator shares are sold /// @param pSharesSold ETH amount of vPool shares sold /// @param id Id of the pool /// @param amountSold ETH amount of shares sold event CommissionSharesSold(uint256 pSharesSold, uint256 id, uint256 amountSold); /// @notice Emitted when new split is set. /// @param split Array of value in basis points to route to each pool event SetPoolPercentages(uint256[] split); /// @notice Thrown when a transfer is attempted but the sender does not have enough balance. /// @param amount The token amount. /// @param balance The balance of user. error InsufficientBalance(uint256 amount, uint256 balance); /// @notice Thrown when a transferFrom is attempted but the spender does not have enough allowance. error InsufficientAllowance(uint256 amount, uint256 allowance); /// @notice Thrown when trying to set a pool percentage != 0 to a deactivated pool error NonZeroPercentageOnDeactivatedPool(uint256 id); /// @notice Set the percentage of new stakes to route to each pool /// @notice If a pool is disabled it needs to be set to 0 in the array /// @param split Array of values in basis points to route to each pool function setPoolPercentages(uint256[] calldata split) external; /// @notice Burns the sender's shares and sends the exitQueue tickets to the caller. /// @param amount Amount of shares to add to the exit queue function requestExit(uint256 amount) external; /// @notice Returns the share to ETH conversion rate /// @return ETH value of a share function rate() external returns (uint256); /// @notice Allows the integrator to prevent users from depositing to a vPool. /// @param poolId The id of the vPool. /// @param status Whether the users can deposit to the pool. /// @param newPoolPercentages Array of value in basis points to route to each pool after the change function setPoolActivation(uint256 poolId, bool status, uint256[] calldata newPoolPercentages) external; } //SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; import "utils.sol/libs/LibPublicKey.sol"; import "utils.sol/libs/LibSignature.sol"; /// @title Custom Types // slither-disable-next-line naming-convention library victypes { struct User4907 { address user; uint64 expiration; } type BalanceMapping is bytes32; type User4907Mapping is bytes32; } //SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; import "./victypes.sol"; /// @title Balance mappings Custom Type library LBalance { // slither-disable-next-line dead-code function get(victypes.BalanceMapping position) internal pure returns (mapping(address => uint256) storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibPublicKey { // slither-disable-next-line unused-state uint256 constant PUBLIC_KEY_LENGTH = 48; // slither-disable-next-line unused-state bytes constant PADDING = hex"00000000000000000000000000000000"; struct PublicKey { bytes32 A; bytes16 B; } // slither-disable-next-line dead-code function toBytes(PublicKey memory publicKey) internal pure returns (bytes memory) { return abi.encodePacked(publicKey.A, publicKey.B); } // slither-disable-next-line dead-code function fromBytes(bytes memory publicKey) internal pure returns (PublicKey memory ret) { publicKey = bytes.concat(publicKey, PADDING); (bytes32 A, bytes32 B_prime) = abi.decode(publicKey, (bytes32, bytes32)); bytes16 B = bytes16(uint128(uint256(B_prime) >> 128)); ret.A = A; ret.B = B; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibSignature { // slither-disable-next-line unused-state uint256 constant SIGNATURE_LENGTH = 96; struct Signature { bytes32 A; bytes32 B; bytes32 C; } // slither-disable-next-line dead-code function toBytes(Signature memory signature) internal pure returns (bytes memory) { return abi.encodePacked(signature.A, signature.B, signature.C); } // slither-disable-next-line dead-code function fromBytes(bytes memory signature) internal pure returns (Signature memory ret) { (ret) = abi.decode(signature, (Signature)); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./libs/LibSanitize.sol"; import "./types/address.sol"; import "./interfaces/IAdministrable.sol"; /// @title Administrable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contract provides all the utilities to handle the administration and its transfer. abstract contract Administrable is IAdministrable { using LAddress for types.Address; /// @dev The admin address in storage. /// @dev Slot: keccak256(bytes("administrable.admin")) - 1 types.Address internal constant $admin = types.Address.wrap(0x927a17e5ea75d9461748062a2652f4d3698a628896c9832f8488fa0d2846af09); /// @dev The pending admin address in storage. /// @dev Slot: keccak256(bytes("administrable.pendingAdmin")) - 1 types.Address internal constant $pendingAdmin = types.Address.wrap(0x3c1eebcc225c6cc7f5f8765767af6eff617b4139dc3624923a2db67dbca7b68e); /// @dev This modifier ensures that only the admin is able to call the method. modifier onlyAdmin() { if (msg.sender != _getAdmin()) { revert LibErrors.Unauthorized(msg.sender, _getAdmin()); } _; } /// @dev This modifier ensures that only the pending admin is able to call the method. modifier onlyPendingAdmin() { if (msg.sender != _getPendingAdmin()) { revert LibErrors.Unauthorized(msg.sender, _getPendingAdmin()); } _; } /// @inheritdoc IAdministrable function admin() external view returns (address) { return _getAdmin(); } /// @inheritdoc IAdministrable function pendingAdmin() external view returns (address) { return _getPendingAdmin(); } /// @notice Propose a new admin. /// @dev Only callable by the admin. /// @param newAdmin The new admin to propose function transferAdmin(address newAdmin) external onlyAdmin { _setPendingAdmin(newAdmin); } /// @notice Accept an admin transfer. /// @dev Only callable by the pending admin. function acceptAdmin() external onlyPendingAdmin { _setAdmin(msg.sender); _setPendingAdmin(address(0)); } /// @dev Retrieve the admin address. /// @return The admin address function _getAdmin() internal view returns (address) { return $admin.get(); } /// @dev Change the admin address. /// @param newAdmin The new admin address function _setAdmin(address newAdmin) internal { LibSanitize.notZeroAddress(newAdmin); emit SetAdmin(newAdmin); $admin.set(newAdmin); } /// @dev Retrieve the pending admin address. /// @return The pending admin address function _getPendingAdmin() internal view returns (address) { return $pendingAdmin.get(); } /// @dev Change the pending admin address. /// @param newPendingAdmin The new pending admin address function _setPendingAdmin(address newPendingAdmin) internal { emit SetPendingAdmin(newPendingAdmin); $pendingAdmin.set(newPendingAdmin); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LBool { // slither-disable-next-line dead-code function get(types.Bool position) internal view returns (bool data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Bool position, bool data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Bool position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CBool { // slither-disable-next-line dead-code function toBytes32(bool val) internal pure returns (bytes32) { return bytes32(toUint256(val)); } // slither-disable-next-line dead-code function toAddress(bool val) internal pure returns (address) { return address(uint160(toUint256(val))); } // slither-disable-next-line dead-code function toUint256(bool val) internal pure returns (uint256 converted) { // slither-disable-next-line assembly assembly { converted := iszero(iszero(val)) } } /// @dev This method should be used to convert a bool to a uint256 when used as a key in a mapping. // slither-disable-next-line dead-code function k(bool val) internal pure returns (uint256) { return toUint256(val); } /// @dev This method should be used to convert a bool to a uint256 when used as a value in a mapping. // slither-disable-next-line dead-code function v(bool val) internal pure returns (uint256) { return toUint256(val); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; /// @notice Library Address - Address slot utilities. library LAddress { // slither-disable-next-line dead-code, assembly function get(types.Address position) internal view returns (address data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Address position, address data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Address position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CAddress { // slither-disable-next-line dead-code function toUint256(address val) internal pure returns (uint256) { return uint256(uint160(val)); } // slither-disable-next-line dead-code function toBytes32(address val) internal pure returns (bytes32) { return bytes32(uint256(uint160(val))); } // slither-disable-next-line dead-code function toBool(address val) internal pure returns (bool converted) { // slither-disable-next-line assembly assembly { converted := gt(val, 0) } } /// @notice This method should be used to convert an address to a uint256 when used as a key in a mapping. // slither-disable-next-line dead-code function k(address val) internal pure returns (uint256) { return toUint256(val); } /// @notice This method should be used to convert an address to a uint256 when used as a value in a mapping. // slither-disable-next-line dead-code function v(address val) internal pure returns (uint256) { return toUint256(val); } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/interfaces/IFixable.sol"; import "../ctypes/ctypes.sol"; /// @title Pool Interface /// @author mortimr @ Kiln /// @notice The vPool contract is in charge of pool funds and fund validators from the vFactory interface IvPool is IFixable { /// @notice Emitted at construction time when all contract addresses are set /// @param factory The address of the vFactory contract /// @param withdrawalRecipient The address of the withdrawal recipient contract /// @param execLayerRecipient The address of the execution layer recipient contract /// @param coverageRecipient The address of the coverage recipient contract /// @param oracleAggregator The address of the oracle aggregator contract /// @param exitQueue The address of the exit queue contract event SetContractLinks( address factory, address withdrawalRecipient, address execLayerRecipient, address coverageRecipient, address oracleAggregator, address exitQueue ); /// @notice Emitted when the global validator extra data is changed /// @param extraData New extra data used on validator purchase event SetValidatorGlobalExtraData(string extraData); /// @notice Emitted when a depositor authorization changed /// @param depositor The address of the depositor /// @param allowed True if allowed to deposit event ApproveDepositor(address depositor, bool allowed); /// @notice Emitted when a depositor performs a deposit /// @param sender The transaction sender /// @param amount The deposit amount /// @param mintedShares The amount of shares created event Deposit(address indexed sender, uint256 amount, uint256 mintedShares); /// @notice Emitted when the vPool purchases validators to the vFactory /// @param validators The list of IDs (not BLS Public keys) event PurchasedValidators(uint256[] validators); /// @notice Emitted when new shares are created /// @param account The account receiving the new shares /// @param amount The amount of shares created /// @param totalSupply The new totalSupply value event Mint(address indexed account, uint256 amount, uint256 totalSupply); /// @notice Emitted when shares are burned /// @param burner The account burning shares /// @param amount The amount of burned shares /// @param totalSupply The new totalSupply value event Burn(address burner, uint256 amount, uint256 totalSupply); /// @notice Emitted when shares are transfered /// @param from The account sending the shares /// @param to The account receiving the shares /// @param value The value transfered event Transfer(address indexed from, address indexed to, uint256 value); /// @notice Emitted when shares are approved for a spender /// @param owner The account approving the shares /// @param spender The account receiving the spending rights /// @param value The value of the approval. Max uint256 means infinite (will never decrease) event Approval(address indexed owner, address indexed spender, uint256 value); /// @notice Emitted when shares are voided (action of burning without redeeming anything on purpose) /// @param voider The account voiding the shares /// @param amount The amount of voided shares event VoidedShares(address voider, uint256 amount); /// @notice Emitted when ether is injected into the system (outside of the deposit flow) /// @param injecter The account injecting the ETH /// @param amount The amount of injected ETH event InjectedEther(address injecter, uint256 amount); /// @notice Emitted when the report processing is finished /// @param epoch The epoch number /// @param report The received report structure /// @param traces Internal traces with key figures event ProcessedReport(uint256 indexed epoch, ctypes.ValidatorsReport report, ReportTraces traces); /// @notice Emitted when rewards are distributed to the node operator /// @param operatorTreasury The address receiving the rewards /// @param sharesCount The amount of shares created to pay the rewards /// @param sharesValue The value in ETH of the newly minted shares /// @param totalSupply The updated totalSupply value /// @param totalUnderlyingSupply The updated totalUnderlyingSupply value event DistributedOperatorRewards( address indexed operatorTreasury, uint256 sharesCount, uint256 sharesValue, uint256 totalSupply, uint256 totalUnderlyingSupply ); /// @notice Emitted when the report bounds are updated /// @param maxAPRUpperBound The maximum APR allowed during oracle reports /// @param maxAPRUpperCoverageBoost The APR boost allowed only for coverage funds /// @param maxRelativeLowerBound The max relative delta in underlying supply authorized during losses of funds event SetReportBounds(uint64 maxAPRUpperBound, uint64 maxAPRUpperCoverageBoost, uint64 maxRelativeLowerBound); /// @notice Emitted when the epochs per frame value is updated /// @param epochsPerFrame The new epochs per frame value event SetEpochsPerFrame(uint256 epochsPerFrame); /// @notice Emitted when the consensus layer spec is updated /// @param consensusLayerSpec The new consensus layer spec event SetConsensusLayerSpec(ctypes.ConsensusLayerSpec consensusLayerSpec); /// @notice Emitted when the operator fee is updated /// @param operatorFeeBps The new operator fee value event SetOperatorFee(uint256 operatorFeeBps); /// @notice Emitted when the deposited ether buffer is updated /// @param depositedEthers The new deposited ethers value event SetDepositedEthers(uint256 depositedEthers); /// @notice Emitted when the committed ether buffer is updated /// @param committedEthers The new committed ethers value event SetCommittedEthers(uint256 committedEthers); /// @notice Emitted when the requested exits is updated /// @param newRequestedExits The new requested exits count event SetRequestedExits(uint32 newRequestedExits); /// @notice The balance was too low for the requested operation /// @param account The account trying to perform the operation /// @param currentBalance The current account balance /// @param requiredAmount The amount that was required to perform the operation error BalanceTooLow(address account, uint256 currentBalance, uint256 requiredAmount); /// @notice The allowance was too low for the requested operation /// @param account The account trying to perform the operation /// @param operator The account triggering the operation on behalf of the account /// @param currentApproval The current account approval towards the operator /// @param requiredAmount The amount that was required to perform the operation error AllowanceTooLow(address account, address operator, uint256 currentApproval, uint256 requiredAmount); /// @notice Thrown when approval for an account and spender is already zero. /// @param account The account for which approval was attempted to be set to zero. /// @param spender The spender for which approval was attempted to be set to zero. error ApprovalAlreadyZero(address account, address spender); /// @notice Thrown when there is an error with a share receiver. /// @param err The error message. error ShareReceiverError(string err); /// @notice Thrown when there is no validator available to purchase. error NoValidatorToPurchase(); /// @notice Thrown when the epoch of a report is too old. /// @param epoch The epoch of the report. /// @param expectEpoch The expected epoch for the operation. error EpochTooOld(uint256 epoch, uint256 expectEpoch); /// @notice Thrown when an epoch is not the first epoch of a frame. /// @param epoch The epoch that was not the first epoch of a frame. error EpochNotFrameFirst(uint256 epoch); /// @notice Thrown when an epoch is not final. /// @param epoch The epoch that was not final. /// @param currentTimestamp The current timestamp. /// @param finalTimestamp The final timestamp of the frame. error EpochNotFinal(uint256 epoch, uint256 currentTimestamp, uint256 finalTimestamp); /// @notice Thrown when the validator count is decreasing. /// @param previousValidatorCount The previous validator count. /// @param validatorCount The current validator count. error DecreasingValidatorCount(uint256 previousValidatorCount, uint256 validatorCount); /// @notice Thrown when the stopped validator count is decreasing. /// @param previousStoppedValidatorCount The previous stopped validator count. /// @param stoppedValidatorCount The current stopped validator count. error DecreasingStoppedValidatorCount(uint256 previousStoppedValidatorCount, uint256 stoppedValidatorCount); /// @notice Thrown when the slashed balance sum is decreasing. /// @param reportedSlashedBalanceSum The reported slashed balance sum. /// @param lastReportedSlashedBalanceSum The last reported slashed balance sum. error DecreasingSlashedBalanceSum(uint256 reportedSlashedBalanceSum, uint256 lastReportedSlashedBalanceSum); /// @notice Thrown when the exited balance sum is decreasing. /// @param reportedExitedBalanceSum The reported exited balance sum. /// @param lastReportedExitedBalanceSum The last reported exited balance sum. error DecreasingExitedBalanceSum(uint256 reportedExitedBalanceSum, uint256 lastReportedExitedBalanceSum); /// @notice Thrown when the skimmed balance sum is decreasing. /// @param reportedSkimmedBalanceSum The reported skimmed balance sum. /// @param lastReportedSkimmedBalanceSum The last reported skimmed balance sum. error DecreasingSkimmedBalanceSum(uint256 reportedSkimmedBalanceSum, uint256 lastReportedSkimmedBalanceSum); /// @notice Thrown when the reported validator count is higher than the total activated validators /// @param stoppedValidatorsCount The reported stopped validator count. /// @param maxStoppedValidatorsCount The maximum allowed stopped validator count. error StoppedValidatorCountTooHigh(uint256 stoppedValidatorsCount, uint256 maxStoppedValidatorsCount); /// @notice Thrown when the reported exiting balance exceeds the total validator balance on the cl /// @param exiting The reported exiting balance. /// @param balance The total validator balance on the cl. error ExitingBalanceTooHigh(uint256 exiting, uint256 balance); /// @notice Thrown when the reported validator count is higher than the deposited validator count. /// @param reportedValidatorCount The reported validator count. /// @param depositedValidatorCount The deposited validator count. error ValidatorCountTooHigh(uint256 reportedValidatorCount, uint256 depositedValidatorCount); /// @notice Thrown when the coverage is higher than the loss. /// @param coverage The coverage. /// @param loss The loss. error CoverageHigherThanLoss(uint256 coverage, uint256 loss); /// @notice Thrown when the balance increase exceeds the maximum allowed balance increase. /// @param balanceIncrease The balance increase. /// @param maximumAllowedBalanceIncrease The maximum allowed balance increase. error UpperBoundCrossed(uint256 balanceIncrease, uint256 maximumAllowedBalanceIncrease); /// @notice Thrown when the balance increase exceeds the maximum allowed balance increase or maximum allowed coverage. /// @param balanceIncrease The balance increase. /// @param maximumAllowedBalanceIncrease The maximum allowed balance increase. /// @param maximumAllowedCoverage The maximum allowed coverage. error BoostedBoundCrossed(uint256 balanceIncrease, uint256 maximumAllowedBalanceIncrease, uint256 maximumAllowedCoverage); /// @notice Thrown when the balance decrease exceeds the maximum allowed balance decrease. /// @param balanceDecrease The balance decrease. /// @param maximumAllowedBalanceDecrease The maximum allowed balance decrease. error LowerBoundCrossed(uint256 balanceDecrease, uint256 maximumAllowedBalanceDecrease); /// @notice Thrown when the amount of shares to mint is computed to 0 error InvalidNullMint(); /// @notice Traces emitted at the end of the reporting process. /// @param preUnderlyingSupply The pre-reporting underlying supply. /// @param postUnderlyingSupply The post-reporting underlying supply. /// @param preSupply The pre-reporting supply. /// @param postSupply The post-reporting supply. /// @param newExitedEthers The new exited ethers. /// @param newSkimmedEthers The new skimmed ethers. /// @param exitBoostEthers The exit boost ethers. /// @param exitFedEthers The exit fed ethers. /// @param exitBurnedShares The exit burned shares. /// @param exitingProjection The exiting projection. /// @param baseFulfillableDemand The base fulfillable demand. /// @param extraFulfillableDemand The extra fulfillable demand. /// @param rewards The rewards. Can be negative when there is a loss, but cannot include coverage funds. /// @param delta The delta. Can be negative when there is a loss and include all pulled funds. /// @param increaseLimit The increase limit. /// @param coverageIncreaseLimit The coverage increase limit. /// @param decreaseLimit The decrease limit. /// @param consensusLayerDelta The consensus layer delta. /// @param pulledCoverageFunds The pulled coverage funds. /// @param pulledExecutionLayerRewards The pulled execution layer rewards. /// @param pulledExitQueueUnclaimedFunds The pulled exit queue unclaimed funds. struct ReportTraces { // supplied uint128 preUnderlyingSupply; uint128 postUnderlyingSupply; uint128 preSupply; uint128 postSupply; // new consensus layer funds uint128 newExitedEthers; uint128 newSkimmedEthers; // exit related funds uint128 exitBoostEthers; uint128 exitFedEthers; uint128 exitBurnedShares; uint128 exitingProjection; uint128 baseFulfillableDemand; uint128 extraFulfillableDemand; // rewards int128 rewards; // delta and details about sources of funds int128 delta; uint128 increaseLimit; uint128 coverageIncreaseLimit; uint128 decreaseLimit; int128 consensusLayerDelta; uint128 pulledCoverageFunds; uint128 pulledExecutionLayerRewards; uint128 pulledExitQueueUnclaimedFunds; } /// @notice Initializes the contract with the given parameters. /// @param addrs The addresses of the dependencies (factory, withdrawal recipient, exec layer recipient, /// coverage recipient, oracle aggregator, exit queue). /// @param epochsPerFrame_ The number of epochs per frame. /// @param consensusLayerSpec_ The consensus layer spec. /// @param bounds_ The bounds for reporting. /// @param operatorFeeBps_ The operator fee in basis points. /// @param extraData_ The initial extra data that will be provided on each deposit function initialize( address[6] calldata addrs, uint256 epochsPerFrame_, ctypes.ConsensusLayerSpec calldata consensusLayerSpec_, uint64[3] calldata bounds_, uint256 operatorFeeBps_, string calldata extraData_ ) external; /// @notice Returns the address of the factory contract. /// @return The address of the factory contract. function factory() external view returns (address); /// @notice Returns the address of the execution layer recipient contract. /// @return The address of the execution layer recipient contract. function execLayerRecipient() external view returns (address); /// @notice Returns the address of the coverage recipient contract. /// @return The address of the coverage recipient contract. function coverageRecipient() external view returns (address); /// @notice Returns the address of the withdrawal recipient contract. /// @return The address of the withdrawal recipient contract. function withdrawalRecipient() external view returns (address); /// @notice Returns the address of the oracle aggregator contract. /// @return The address of the oracle aggregator contract. function oracleAggregator() external view returns (address); /// @notice Returns the address of the exit queue contract /// @return The address of the exit queue contract function exitQueue() external view returns (address); /// @notice Returns the current validator global extra data /// @return The validator global extra data value function validatorGlobalExtraData() external view returns (string memory); /// @notice Returns whether the given address is a depositor. /// @param depositorAddress The address to check. /// @return Whether the given address is a depositor. function depositors(address depositorAddress) external view returns (bool); /// @notice Returns the total supply of tokens. /// @return The total supply of tokens. function totalSupply() external view returns (uint256); /// @notice Returns the name of the vPool /// @return The name of the vPool function name() external view returns (string memory); /// @notice Returns the symbol of the vPool /// @return The symbol of the vPool function symbol() external view returns (string memory); /// @notice Returns the decimals of the vPool shares /// @return The decimal count function decimals() external pure returns (uint8); /// @notice Returns the total underlying supply of tokens. /// @return The total underlying supply of tokens. function totalUnderlyingSupply() external view returns (uint256); /// @notice Returns the current ETH/SHARES rate based on the total underlying supply and total supply. /// @return The current rate function rate() external view returns (uint256); /// @notice Returns the current requested exit count /// @return The current requested exit count function requestedExits() external view returns (uint32); /// @notice Returns the balance of the given account. /// @param account The address of the account to check. /// @return The balance of the given account. function balanceOf(address account) external view returns (uint256); /// @notice Returns the allowance of the given spender for the given owner. /// @param owner The owner of the allowance. /// @param spender The spender of the allowance. /// @return The allowance of the given spender for the given owner. function allowance(address owner, address spender) external view returns (uint256); /// @notice Returns the details about the held ethers /// @return The structure of ethers inside the contract function ethers() external view returns (ctypes.Ethers memory); /// @notice Returns an array of the IDs of purchased validators. /// @return An array of the IDs of purchased validators. function purchasedValidators() external view returns (uint256[] memory); /// @notice Returns the ID of the purchased validator at the given index. /// @param idx The index of the validator. /// @return The ID of the purchased validator at the given index. function purchasedValidatorAtIndex(uint256 idx) external view returns (uint256); /// @notice Returns the total number of purchased validators. /// @return The total number of purchased validators. function purchasedValidatorCount() external view returns (uint256); /// @notice Returns the last epoch. /// @return The last epoch. function lastEpoch() external view returns (uint256); /// @notice Returns the last validator report that was processed /// @return The last report structure. function lastReport() external view returns (ctypes.ValidatorsReport memory); /// @notice Returns the total amount in ETH covered by the contract. /// @return The total amount in ETH covered by the contract. function totalCovered() external view returns (uint256); /// @notice Returns the number of epochs per frame. /// @return The number of epochs per frame. function epochsPerFrame() external view returns (uint256); /// @notice Returns the consensus layer spec. /// @return The consensus layer spec. function consensusLayerSpec() external pure returns (ctypes.ConsensusLayerSpec memory); /// @notice Returns the report bounds. /// @return maxAPRUpperBound The maximum APR for the upper bound. /// @return maxAPRUpperCoverageBoost The maximum APR for the upper bound with coverage boost. /// @return maxRelativeLowerBound The maximum relative lower bound. function reportBounds() external view returns (uint64 maxAPRUpperBound, uint64 maxAPRUpperCoverageBoost, uint64 maxRelativeLowerBound); /// @notice Returns the operator fee. /// @return The operator fee. function operatorFee() external view returns (uint256); /// @notice Returns whether the given epoch is valid. /// @param epoch The epoch to check. /// @return Whether the given epoch is valid. function isValidEpoch(uint256 epoch) external view returns (bool); /// @notice Reverts if given epoch is invalid, with an explicit custom error based on the issue /// @param epoch The epoch to check. function onlyValidEpoch(uint256 epoch) external view; /// @notice Allows or disallows the given depositor to deposit. /// @param depositorAddress The address of the depositor. /// @param allowed Whether the depositor is allowed to deposit. function allowDepositor(address depositorAddress, bool allowed) external; /// @notice Transfers the given amount of shares to the given address. /// @param to The address to transfer the shares to. /// @param amount The amount of shares to transfer. /// @param data Additional data for the transfer. /// @return Whether the transfer was successful. function transferShares(address to, uint256 amount, bytes calldata data) external returns (bool); /// @notice Increases the allowance for the given spender by the given amount. /// @param spender The spender to increase the allowance for. /// @param amount The amount to increase the allowance by. /// @return Whether the increase was successful. function increaseAllowance(address spender, uint256 amount) external returns (bool); /// @notice Decreases the allowance of a spender by the given amount. /// @param spender The address of the spender. /// @param amount The amount to decrease the allowance by. /// @return Whether the allowance was successfully decreased. function decreaseAllowance(address spender, uint256 amount) external returns (bool); /// @notice Voids the allowance of a spender. /// @param spender The address of the spender. /// @return Whether the allowance was successfully voided. function voidAllowance(address spender) external returns (bool); /// @notice Transfers shares from one account to another. /// @param from The address of the account to transfer shares from. /// @param to The address of the account to transfer shares to. /// @param amount The amount of shares to transfer. /// @param data Optional data to include with the transaction. /// @return Whether the transfer was successful. function transferSharesFrom(address from, address to, uint256 amount, bytes calldata data) external returns (bool); /// @notice Deposits ether into the contract. /// @return The number of shares minted on deposit function deposit() external payable returns (uint256); /// @notice Purchases the maximum number of validators allowed. /// @param max The maximum number of validators to purchase. function purchaseValidators(uint256 max) external; /// @notice Sets the operator fee. /// @param operatorFeeBps The new operator fee, in basis points. function setOperatorFee(uint256 operatorFeeBps) external; /// @notice Sets the number of epochs per frame. /// @param newEpochsPerFrame The new number of epochs per frame. function setEpochsPerFrame(uint256 newEpochsPerFrame) external; /// @notice Sets the consensus layer spec. /// @param consensusLayerSpec_ The new consensus layer spec. function setConsensusLayerSpec(ctypes.ConsensusLayerSpec calldata consensusLayerSpec_) external; /// @notice Sets the global validator extra data /// @param extraData The new extra data to use function setValidatorGlobalExtraData(string calldata extraData) external; /// @notice Sets the bounds for reporting. /// @param maxAPRUpperBound The maximum APR for the upper bound. /// @param maxAPRUpperCoverageBoost The maximum APR for the upper coverage boost. /// @param maxRelativeLowerBound The maximum relative value for the lower bound. function setReportBounds(uint64 maxAPRUpperBound, uint64 maxAPRUpperCoverageBoost, uint64 maxRelativeLowerBound) external; /// @notice Injects ether into the contract. function injectEther() external payable; /// @notice Voids the given amount of shares. /// @param amount The amount of shares to void. function voidShares(uint256 amount) external; /// @notice Reports the validator data for the given epoch. /// @param rprt The consensus layer report to process function report(ctypes.ValidatorsReport calldata rprt) external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "./IFeeDispatcher.sol"; import "vsuite/interfaces/IvPoolSharesReceiver.sol"; /// @notice PoolStakeDetails contains the details of a stake /// @param poolId Id of the pool /// @param ethToPool ETH amount sent to the pool /// @param ethToIntegrator ETH amount going to the integrator /// @param pSharesFromPool Amount of pool shares received from the pool /// @param pSharesFromIntegrator Amount of pool shares received from the integrator struct PoolStakeDetails { uint128 poolId; uint128 ethToPool; uint128 ethToIntegrator; uint128 pSharesFromPool; uint128 pSharesFromIntegrator; } /// @notice PoolExitDetails contains the details of an exit /// @param poolId Id of the pool /// @param exitedPoolShares Amount of pool shares exited struct PoolExitDetails { uint128 poolId; uint128 exitedPoolShares; } /// @title MultiPool (V1) Interface /// @author 0xvv @ Kiln /// @notice This contract contains the common functions to all integration contracts. /// Contains the functions to add pools, activate/deactivate a pool, change the fee of a pool and change the commission distribution. interface IMultiPool is IFeeDispatcher, IvPoolSharesReceiver { /// @notice Emitted when vPool shares are received /// @param vPool Address of the vPool sending the shares /// @param poolId Id of the pool in the integrations contract /// @param amount The amount of vPool shares received event VPoolSharesReceived(address vPool, uint256 poolId, uint256 amount); /// @notice Emitted when a vPool in enabled or disabled /// @param poolAddress The new pool address /// @param id Id of the pool /// @param isActive whether the pool can be staked to or not event PoolActivation(address poolAddress, uint256 id, bool isActive); /// @notice Emitted when a vPool address is added to vPools /// @param poolAddress The new pool address /// @param id Id of the pool event PoolAdded(address poolAddress, uint256 id); /// @notice Emitted when the integrator fee is changed /// @param poolId Id of the pool /// @param operatorFeeBps The new fee in basis points event SetFee(uint256 poolId, uint256 operatorFeeBps); /// @notice Emitted when the display name is changed /// @param name The new name event SetName(string name); /// @notice Emitted when the display symbol is changed /// @param symbol The new display symbol event SetSymbol(string symbol); /// @notice Emitted when the max commission is set /// @param maxCommission The new max commission event SetMaxCommission(uint256 maxCommission); /// @notice Emitted when the deposits are paused or unpaused /// @param isPaused Whether the deposits are paused or not event SetDepositsPaused(bool isPaused); /// @notice Emitted when staking occurs, contains the details for all the pools /// @param staker The address staking /// @param depositedEth The amount of ETH staked /// @param mintedTokens The amount of integrator shares minted /// @param stakeDetails Array of details for each pool, contains the pool id, the amount of ETH sent to the pool, /// the amount of ETH sent to the integrator, the amount of pool shares received from the pool and /// the amount of pools shares bought from the integrator event Stake(address indexed staker, uint128 depositedEth, uint128 mintedTokens, PoolStakeDetails[] stakeDetails); /// @notice Emitted when an exit occurs, contains the details for all the pools /// @param staker The address exiting /// @param exitedTokens The amount of integrator shares exited /// @param exitDetails Array of details for each pool, contains the pool id and the amount of pool shares exited event Exit(address indexed staker, uint128 exitedTokens, PoolExitDetails[] exitDetails); /// @notice Emitted when the commission is distributed via a manual call /// @param poolId Id of the pool /// @param shares Amount of pool shares exited /// @param weights Array of weights for each recipient /// @param recipients Array of recipients event ExitedCommissionShares(uint256 indexed poolId, uint256 shares, uint256[] weights, address[] recipients); /// @notice Thrown on stake if deposits are paused error DepositsPaused(); /// @notice Thrown when trying to stake but the sum of amounts is not equal to the msg.value /// @param sum Sum of amounts in the list /// @param msgValue Amount of ETH sent error InvalidAmounts(uint256 sum, uint256 msgValue); /// @notice Thrown when trying to init the contract without providing a pool address error EmptyPoolList(); /// @notice Thrown when trying to change the fee but there are integrator shares left to sell /// @param ethLeft The ETH value of shares left to sell /// @param id Id of the pool error OutstandingCommission(uint256 ethLeft, uint256 id); /// @notice Thrown when trying to add a Pool that is already registered in the contract /// @param newPool The pool address error PoolAlreadyRegistered(address newPool); /// @notice Thrown when trying to deposit to a disabled pool /// @param poolId Id of the pool error PoolDisabled(uint256 poolId); /// @notice Thrown when trying the pool shares callback is called by an address that is not registered /// @param poolAddress The pool address error NotARegisteredPool(address poolAddress); /// @notice Emitted when a pool transfer does not return true. /// @param id The id of the pool. error PoolTransferFailed(uint256 id); /// @notice Thrown when passing an invalid poolId /// @param poolId Invalid pool id error InvalidPoolId(uint256 poolId); /// @notice Thrown when the commission underflow when lowering the fee /// @notice To avoid this, the integrator can call exitCommissionShares before lowering the fee or wait for the integrator shares to be sold error CommissionPaidUnderflow(); /// @notice Thrown when minting a null amount of shares error ZeroSharesMint(); /// @notice Thrown when trying to see a fee over the max fee set at initialization error FeeOverMax(uint256 maxFeeBps); /// @notice Thrown when trying to call the callback outside of the minting process error CallbackNotFromMinting(); /// @notice Thrown when trying to exit the commission shares but there are no shares to exit error NoSharesToExit(uint256 poolId); /// @notice Returns the list of vPools. /// @return vPools The addresses of the pool contract. function pools() external view returns (address[] memory vPools); /// @notice Returns the current fee in basis points for the given pool. /// @return feeBps The current fee in basis points. /// @param id Id of the pool function getFee(uint256 id) external view returns (uint256 feeBps); /// @notice Allows the integrator to change the fee. /// @dev Reverts if there are unsold integrator shares. /// @param poolId vPool id /// @param newFeeBps The new fee in basis points. function changeFee(uint256 poolId, uint256 newFeeBps) external; /// @notice Allows the admin to change the fee sharing upon withdrawal. /// @param recipients The list of fee recipients. /// @param splits List of each recipient share in basis points. function changeSplit(address[] calldata recipients, uint256[] calldata splits) external; /// @notice Allows the integrator to add a vPool. /// @dev Reverts if the pool is already in the pools list. /// @param newPool The address of the new vPool. /// @param fee The fee to be applied to rewards from this vPool, in basis points. function addPool(address newPool, uint256 fee) external; /// @notice Returns true if the pool is active, false otherwise /// @param poolId The id of the vPool. function getPoolActivation(uint256 poolId) external view returns (bool); /// @notice Returns the ETH value of integrator shares left to sell. /// @param poolId The id of the vPool. /// @return The ETH value of unsold integrator shares. function integratorCommissionOwed(uint256 poolId) external view returns (uint256); /// @notice Allows the integrator to exit the integrator shares of a vPool. /// @param poolId The id of the vPool. function exitCommissionShares(uint256 poolId) external; /// @notice Allows the integrator to pause and unpause deposits only. /// @param isPaused Whether the deposits are paused or not. function pauseDeposits(bool isPaused) external; /// @notice Returns true if deposits are paused, false otherwise function depositsPaused() external view returns (bool); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/libs/LibErrors.sol"; import "utils.sol/libs/LibUint256.sol"; import "utils.sol/libs/LibConstant.sol"; import "utils.sol/types/array.sol"; import "utils.sol/types/uint256.sol"; import "./interfaces/IFeeDispatcher.sol"; /// @title FeeDispatcher (V1) Contract /// @author 0xvv @ Kiln /// @notice This contract contains functions to dispatch the ETH in a contract upon withdrawal. // slither-disable-next-line naming-convention abstract contract FeeDispatcher is IFeeDispatcher { using LArray for types.Array; using LUint256 for types.Uint256; /// @dev The recipients of the fees upon withdrawal. /// @dev Slot: keccak256(bytes("feeDispatcher.1.feeRecipients")) - 1 types.Array internal constant $feeRecipients = types.Array.wrap(0xd681f9d3e640a2dd835404271506ef93f020e2fc065878793505e5ea088fde3d); /// @dev The splits of each recipient of the fees upon withdrawal. /// @dev Slot: keccak256(bytes("feeDispatcher.1.feeSplits")) - 1 types.Array internal constant $feeSplits = types.Array.wrap(0x31a3fa329157566a07927d0c2ba92ff801e4db8af2ec73f92eaf3e7f78d587a8); /// @dev The lock to prevent reentrancy /// @dev Slot: keccak256(bytes("feeDispatcher.1.locked")) - 1 types.Uint256 internal constant $locked = types.Uint256.wrap(0x8472de2bbf04bc62a7ee894bd625126d381bf5e8b726e5cd498c3a9dad76d85b); /// @dev The states of the lock, 1 = unlocked, 2 = locked uint256 internal constant UNLOCKED = 1; uint256 internal constant LOCKED = 2; constructor() { $locked.set(LOCKED); } /// @dev An internal function to set the fee split & unlock the reentrancy lock. /// Should be called in the initializer of the inheriting contract. // slither-disable-next-line dead-code function _initFeeDispatcher(address[] calldata recipients, uint256[] calldata splits) internal { _setFeeSplit(recipients, splits); $locked.set(UNLOCKED); } /// @notice Modifier to prevent reentrancy modifier nonReentrant() virtual { if ($locked.get() == LOCKED) { revert Reentrancy(); } $locked.set(LOCKED); _; $locked.set(UNLOCKED); } /// @inheritdoc IFeeDispatcher // slither-disable-next-line low-level-calls,calls-loop,reentrancy-events,assembly function withdrawCommission() external nonReentrant { uint256 balance = address(this).balance; address[] memory recipients = $feeRecipients.toAddressA(); uint256[] memory splits = $feeSplits.toUintA(); for (uint256 i = 0; i < recipients.length;) { uint256 share = LibUint256.mulDiv(balance, splits[i], LibConstant.BASIS_POINTS_MAX); address recipient = recipients[i]; emit CommissionWithdrawn(recipient, share); (bool success, bytes memory rdata) = recipient.call{value: share}(""); if (!success) { assembly { revert(add(32, rdata), mload(rdata)) } } unchecked { i++; } } } /// @notice Returns the current fee split and recipients /// @return feeRecipients The current fee recipients /// @return feeSplits The current fee splits /// @dev This function is not pure as it fetches the current fee split and recipients from storage function getCurrentSplit() external pure returns (address[] memory, uint256[] memory) { return ($feeRecipients.toAddressA(), $feeSplits.toUintA()); } /// @dev Internal utility to set the fee distribution upon withdrawal /// @param recipients The new fee recipients list /// @param splits The new split between fee recipients // slither-disable-next-line dead-code function _setFeeSplit(address[] calldata recipients, uint256[] calldata splits) internal { if (recipients.length != splits.length) { revert UnequalLengths(recipients.length, splits.length); } $feeSplits.del(); $feeRecipients.del(); uint256 sum; for (uint256 i = 0; i < recipients.length; i++) { uint256 split = splits[i]; sum += split; $feeSplits.toUintA().push(split); $feeRecipients.toAddressA().push(recipients[i]); } if (sum != LibConstant.BASIS_POINTS_MAX) { revert LibErrors.InvalidBPSValue(); } emit NewCommissionSplit(recipients, splits); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/libs/LibErrors.sol"; import "utils.sol/libs/LibUint256.sol"; import "utils.sol/libs/LibConstant.sol"; import "./interfaces/IExitQueueClaimHelper.sol"; import "./interfaces/IFeeDispatcher.sol"; /// @title ExitQueueClaimeHelper (V1) Contract /// @author gauthiermyr @ Kiln /// @notice This contract contains functions to resolve and claim casks on several exit queues. contract ExitQueueClaimHelper is IExitQueueClaimHelper { /// @inheritdoc IExitQueueClaimHelper function multiResolve(address[] calldata exitQueues, uint256[][] calldata ticketIds) external view override returns (int64[][] memory caskIdsOrErrors) { if (exitQueues.length != ticketIds.length) { revert IFeeDispatcher.UnequalLengths(exitQueues.length, ticketIds.length); } caskIdsOrErrors = new int64[][](exitQueues.length); for (uint256 i = 0; i < exitQueues.length;) { IvExitQueue exitQueue = IvExitQueue(exitQueues[i]); // slither-disable-next-line calls-loop caskIdsOrErrors[i] = exitQueue.resolve(ticketIds[i]); unchecked { ++i; } } } /// @inheritdoc IExitQueueClaimHelper function multiClaim(address[] calldata exitQueues, uint256[][] calldata ticketIds, uint32[][] calldata casksIds) external override returns (IvExitQueue.ClaimStatus[][] memory statuses) { if (exitQueues.length != ticketIds.length) { revert IFeeDispatcher.UnequalLengths(exitQueues.length, ticketIds.length); } if (exitQueues.length != casksIds.length) { revert IFeeDispatcher.UnequalLengths(exitQueues.length, casksIds.length); } statuses = new IvExitQueue.ClaimStatus[][](exitQueues.length); for (uint256 i = 0; i < exitQueues.length;) { IvExitQueue exitQueue = IvExitQueue(exitQueues[i]); // slither-disable-next-line calls-loop statuses[i] = exitQueue.claim(ticketIds[i], casksIds[i], type(uint16).max); unchecked { ++i; } } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./LibErrors.sol"; import "./LibConstant.sol"; /// @title Lib Sanitize /// @dev This library helps sanitizing inputs. library LibSanitize { /// @dev Internal utility to sanitize an address and ensure its value is not 0. /// @param addressValue The address to verify // slither-disable-next-line dead-code function notZeroAddress(address addressValue) internal pure { if (addressValue == address(0)) { revert LibErrors.InvalidZeroAddress(); } } /// @dev Internal utility to sanitize an uint256 value and ensure its value is not 0. /// @param value The value to verify // slither-disable-next-line dead-code function notNullValue(uint256 value) internal pure { if (value == 0) { revert LibErrors.InvalidNullValue(); } } /// @dev Internal utility to sanitize a bps value and ensure it's <= 100%. /// @param value The bps value to verify // slither-disable-next-line dead-code function notInvalidBps(uint256 value) internal pure { if (value > LibConstant.BASIS_POINTS_MAX) { revert LibErrors.InvalidBPSValue(); } } /// @dev Internal utility to sanitize a string value and ensure it's not empty. /// @param stringValue The string value to verify // slither-disable-next-line dead-code function notEmptyString(string memory stringValue) internal pure { if (bytes(stringValue).length == 0) { revert LibErrors.InvalidEmptyString(); } } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Administrable Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contract provides all the utilities to handle the administration and its transfer. interface IAdministrable { /// @notice The admin address has been changed. /// @param admin The new admin address event SetAdmin(address admin); /// @notice The pending admin address has been changed. /// @param pendingAdmin The pending admin has been changed event SetPendingAdmin(address pendingAdmin); /// @notice Retrieve the admin address. /// @return adminAddress The admin address function admin() external view returns (address adminAddress); /// @notice Retrieve the pending admin address. /// @return pendingAdminAddress The pending admin address function pendingAdmin() external view returns (address pendingAdminAddress); /// @notice Propose a new admin. /// @dev Only callable by the admin /// @param _newAdmin The new admin to propose function transferAdmin(address _newAdmin) external; /// @notice Accept an admin transfer. /// @dev Only callable by the pending admin function acceptAdmin() external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Fixable Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Fixable contract can be used on cubs to expose a safe noop to force a fix. interface IFixable { /// @notice Noop method to force a global fix to be applied. function fix() external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/interfaces/IFixable.sol"; /// @title FeeDispatcher (V1) Interface /// @author 0xvv @ Kiln /// @notice This contract contains functions to dispatch the ETH in a contract upon withdrawal. interface IFeeDispatcher { /// @notice Emitted when the commission split is changed. /// @param recipients The addresses of recipients /// @param splits The percentage of each recipient in basis points event NewCommissionSplit(address[] recipients, uint256[] splits); /// @notice Emitted when the integrator withdraws ETH /// @param withdrawer address withdrawing the ETH /// @param amountWithdrawn amount of ETH withdrawn event CommissionWithdrawn(address indexed withdrawer, uint256 amountWithdrawn); /// @notice Thrown when functions are given lists of different length in batch arguments /// @param lengthA First argument length /// @param lengthB Second argument length error UnequalLengths(uint256 lengthA, uint256 lengthB); /// @notice Thrown when a function is called while the contract is locked error Reentrancy(); /// @notice Allows the integrator to withdraw the ETH in the contract. function withdrawCommission() external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; /// @title Pool Shares Receiver Interface /// @author mortimr @ Kiln /// @notice Interface that needs to be implemented for a contract to be able to receive shares interface IvPoolSharesReceiver { /// @notice Callback used by the vPool to notify contracts of shares being transfered /// @param operator The address of the operator of the transfer /// @param from The address sending the funds /// @param amount The amount of shares received /// @param data The attached data /// @return selector Should return its own selector if everything went well function onvPoolSharesReceived(address operator, address from, uint256 amount, bytes memory data) external returns (bytes4 selector); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibErrors { error Unauthorized(address account, address expected); error InvalidZeroAddress(); error InvalidNullValue(); error InvalidBPSValue(); error InvalidEmptyString(); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "prb-math/PRBMath.sol"; library LibUint256 { // slither-disable-next-line dead-code function min(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly // slither-disable-next-line assembly assembly { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @custom:author Vectorized/solady#58681e79de23082fd3881a76022e0842f5c08db8 // slither-disable-next-line dead-code function max(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly // slither-disable-next-line assembly assembly { z := xor(x, mul(xor(x, y), gt(y, x))) } } // slither-disable-next-line dead-code function mulDiv(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) { return PRBMath.mulDiv(a, b, c); } // slither-disable-next-line dead-code function ceil(uint256 num, uint256 den) internal pure returns (uint256) { return (num / den) + (num % den > 0 ? 1 : 0); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibConstant { /// @dev The basis points value representing 100%. uint256 internal constant BASIS_POINTS_MAX = 10_000; /// @dev The size of a deposit to activate a validator. uint256 internal constant DEPOSIT_SIZE = 32 ether; /// @dev The minimum freeze timeout before freeze is active. uint256 internal constant MINIMUM_FREEZE_TIMEOUT = 100 days; /// @dev Address used to represent ETH when an address is required to identify an asset. address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LArray { // slither-disable-next-line dead-code function toUintA(types.Array position) internal pure returns (uint256[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function toAddressA(types.Array position) internal pure returns (address[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function toBoolA(types.Array position) internal pure returns (bool[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function toBytes32A(types.Array position) internal pure returns (bytes32[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function del(types.Array position) internal { // slither-disable-next-line assembly assembly { let len := sload(position) if len { // clear the length slot sstore(position, 0) // calculate the starting slot of the array elements in storage mstore(0, position) let startPtr := keccak256(0, 0x20) for {} len {} { len := sub(len, 1) sstore(add(startPtr, len), 0) } } } } /// @dev This delete can be used if and only if we only want to clear the length of the array. /// Doing so will create an array that behaves like an empty array in solidity. /// It can have advantages if we often rewrite to the same slots of the array. /// Prefer using `del` if you don't know what you're doing. // slither-disable-next-line dead-code function dangerousDirtyDel(types.Array position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "vsuite/interfaces/IvExitQueue.sol"; /// @title ExitQueueClaimeHelper (V1) Interface /// @author gauthiermyr @ Kiln interface IExitQueueClaimHelper { /// @notice Resolve a list of casksIds for given exitQueues and tickets /// @param exitQueues List of exit queues /// @param ticketIds List of tickets in each exit queue function multiResolve(address[] calldata exitQueues, uint256[][] calldata ticketIds) external view returns (int64[][] memory caskIdsOrErrors); /// @notice Claim caskIds for given tickets on each exit queue /// @param exitQueues List of exit queues /// @param ticketIds List of tickets in each exit queue /// @param casksIds List of caskIds to claim with each ticket function multiClaim(address[] calldata exitQueues, uint256[][] calldata ticketIds, uint32[][] calldata casksIds) external returns (IvExitQueue.ClaimStatus[][] memory statuse); } // SPDX-License-Identifier: Unlicense pragma solidity >=0.8.4; /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivFixedPointOverflow(uint256 prod1); /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivOverflow(uint256 prod1, uint256 denominator); /// @notice Emitted when one of the inputs is type(int256).min. error PRBMath__MulDivSignedInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows int256. error PRBMath__MulDivSignedOverflow(uint256 rAbs); /// @notice Emitted when the input is MIN_SD59x18. error PRBMathSD59x18__AbsInputTooSmall(); /// @notice Emitted when ceiling a number overflows SD59x18. error PRBMathSD59x18__CeilOverflow(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__DivInputTooSmall(); /// @notice Emitted when one of the intermediary unsigned results overflows SD59x18. error PRBMathSD59x18__DivOverflow(uint256 rAbs); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathSD59x18__ExpInputTooBig(int256 x); /// @notice Emitted when the input is greater than 192. error PRBMathSD59x18__Exp2InputTooBig(int256 x); /// @notice Emitted when flooring a number underflows SD59x18. error PRBMathSD59x18__FloorUnderflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format overflows SD59x18. error PRBMathSD59x18__FromIntOverflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format underflows SD59x18. error PRBMathSD59x18__FromIntUnderflow(int256 x); /// @notice Emitted when the product of the inputs is negative. error PRBMathSD59x18__GmNegativeProduct(int256 x, int256 y); /// @notice Emitted when multiplying the inputs overflows SD59x18. error PRBMathSD59x18__GmOverflow(int256 x, int256 y); /// @notice Emitted when the input is less than or equal to zero. error PRBMathSD59x18__LogInputTooSmall(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__MulInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__MulOverflow(uint256 rAbs); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__PowuOverflow(uint256 rAbs); /// @notice Emitted when the input is negative. error PRBMathSD59x18__SqrtNegativeInput(int256 x); /// @notice Emitted when the calculating the square root overflows SD59x18. error PRBMathSD59x18__SqrtOverflow(int256 x); /// @notice Emitted when addition overflows UD60x18. error PRBMathUD60x18__AddOverflow(uint256 x, uint256 y); /// @notice Emitted when ceiling a number overflows UD60x18. error PRBMathUD60x18__CeilOverflow(uint256 x); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathUD60x18__ExpInputTooBig(uint256 x); /// @notice Emitted when the input is greater than 192. error PRBMathUD60x18__Exp2InputTooBig(uint256 x); /// @notice Emitted when converting a basic integer to the fixed-point format format overflows UD60x18. error PRBMathUD60x18__FromUintOverflow(uint256 x); /// @notice Emitted when multiplying the inputs overflows UD60x18. error PRBMathUD60x18__GmOverflow(uint256 x, uint256 y); /// @notice Emitted when the input is less than 1. error PRBMathUD60x18__LogInputTooSmall(uint256 x); /// @notice Emitted when the calculating the square root overflows UD60x18. error PRBMathUD60x18__SqrtOverflow(uint256 x); /// @notice Emitted when subtraction underflows UD60x18. error PRBMathUD60x18__SubUnderflow(uint256 x, uint256 y); /// @dev Common mathematical functions used in both PRBMathSD59x18 and PRBMathUD60x18. Note that this shared library /// does not always assume the signed 59.18-decimal fixed-point or the unsigned 60.18-decimal fixed-point /// representation. When it does not, it is explicitly mentioned in the NatSpec documentation. library PRBMath { /// STRUCTS /// struct SD59x18 { int256 value; } struct UD60x18 { uint256 value; } /// STORAGE /// /// @dev How many trailing decimals can be represented. uint256 internal constant SCALE = 1e18; /// @dev Largest power of two divisor of SCALE. uint256 internal constant SCALE_LPOTD = 262144; /// @dev SCALE inverted mod 2^256. uint256 internal constant SCALE_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281; /// FUNCTIONS /// /// @notice Calculates the binary exponent of x using the binary fraction method. /// @dev Has to use 192.64-bit fixed-point numbers. /// See https://ethereum.stackexchange.com/a/96594/24693. /// @param x The exponent as an unsigned 192.64-bit fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function exp2(uint256 x) internal pure returns (uint256 result) { unchecked { // Start from 0.5 in the 192.64-bit fixed-point format. result = 0x800000000000000000000000000000000000000000000000; // Multiply the result by root(2, 2^-i) when the bit at position i is 1. None of the intermediary results overflows // because the initial result is 2^191 and all magic factors are less than 2^65. if (x & 0x8000000000000000 > 0) { result = (result * 0x16A09E667F3BCC909) >> 64; } if (x & 0x4000000000000000 > 0) { result = (result * 0x1306FE0A31B7152DF) >> 64; } if (x & 0x2000000000000000 > 0) { result = (result * 0x1172B83C7D517ADCE) >> 64; } if (x & 0x1000000000000000 > 0) { result = (result * 0x10B5586CF9890F62A) >> 64; } if (x & 0x800000000000000 > 0) { result = (result * 0x1059B0D31585743AE) >> 64; } if (x & 0x400000000000000 > 0) { result = (result * 0x102C9A3E778060EE7) >> 64; } if (x & 0x200000000000000 > 0) { result = (result * 0x10163DA9FB33356D8) >> 64; } if (x & 0x100000000000000 > 0) { result = (result * 0x100B1AFA5ABCBED61) >> 64; } if (x & 0x80000000000000 > 0) { result = (result * 0x10058C86DA1C09EA2) >> 64; } if (x & 0x40000000000000 > 0) { result = (result * 0x1002C605E2E8CEC50) >> 64; } if (x & 0x20000000000000 > 0) { result = (result * 0x100162F3904051FA1) >> 64; } if (x & 0x10000000000000 > 0) { result = (result * 0x1000B175EFFDC76BA) >> 64; } if (x & 0x8000000000000 > 0) { result = (result * 0x100058BA01FB9F96D) >> 64; } if (x & 0x4000000000000 > 0) { result = (result * 0x10002C5CC37DA9492) >> 64; } if (x & 0x2000000000000 > 0) { result = (result * 0x1000162E525EE0547) >> 64; } if (x & 0x1000000000000 > 0) { result = (result * 0x10000B17255775C04) >> 64; } if (x & 0x800000000000 > 0) { result = (result * 0x1000058B91B5BC9AE) >> 64; } if (x & 0x400000000000 > 0) { result = (result * 0x100002C5C89D5EC6D) >> 64; } if (x & 0x200000000000 > 0) { result = (result * 0x10000162E43F4F831) >> 64; } if (x & 0x100000000000 > 0) { result = (result * 0x100000B1721BCFC9A) >> 64; } if (x & 0x80000000000 > 0) { result = (result * 0x10000058B90CF1E6E) >> 64; } if (x & 0x40000000000 > 0) { result = (result * 0x1000002C5C863B73F) >> 64; } if (x & 0x20000000000 > 0) { result = (result * 0x100000162E430E5A2) >> 64; } if (x & 0x10000000000 > 0) { result = (result * 0x1000000B172183551) >> 64; } if (x & 0x8000000000 > 0) { result = (result * 0x100000058B90C0B49) >> 64; } if (x & 0x4000000000 > 0) { result = (result * 0x10000002C5C8601CC) >> 64; } if (x & 0x2000000000 > 0) { result = (result * 0x1000000162E42FFF0) >> 64; } if (x & 0x1000000000 > 0) { result = (result * 0x10000000B17217FBB) >> 64; } if (x & 0x800000000 > 0) { result = (result * 0x1000000058B90BFCE) >> 64; } if (x & 0x400000000 > 0) { result = (result * 0x100000002C5C85FE3) >> 64; } if (x & 0x200000000 > 0) { result = (result * 0x10000000162E42FF1) >> 64; } if (x & 0x100000000 > 0) { result = (result * 0x100000000B17217F8) >> 64; } if (x & 0x80000000 > 0) { result = (result * 0x10000000058B90BFC) >> 64; } if (x & 0x40000000 > 0) { result = (result * 0x1000000002C5C85FE) >> 64; } if (x & 0x20000000 > 0) { result = (result * 0x100000000162E42FF) >> 64; } if (x & 0x10000000 > 0) { result = (result * 0x1000000000B17217F) >> 64; } if (x & 0x8000000 > 0) { result = (result * 0x100000000058B90C0) >> 64; } if (x & 0x4000000 > 0) { result = (result * 0x10000000002C5C860) >> 64; } if (x & 0x2000000 > 0) { result = (result * 0x1000000000162E430) >> 64; } if (x & 0x1000000 > 0) { result = (result * 0x10000000000B17218) >> 64; } if (x & 0x800000 > 0) { result = (result * 0x1000000000058B90C) >> 64; } if (x & 0x400000 > 0) { result = (result * 0x100000000002C5C86) >> 64; } if (x & 0x200000 > 0) { result = (result * 0x10000000000162E43) >> 64; } if (x & 0x100000 > 0) { result = (result * 0x100000000000B1721) >> 64; } if (x & 0x80000 > 0) { result = (result * 0x10000000000058B91) >> 64; } if (x & 0x40000 > 0) { result = (result * 0x1000000000002C5C8) >> 64; } if (x & 0x20000 > 0) { result = (result * 0x100000000000162E4) >> 64; } if (x & 0x10000 > 0) { result = (result * 0x1000000000000B172) >> 64; } if (x & 0x8000 > 0) { result = (result * 0x100000000000058B9) >> 64; } if (x & 0x4000 > 0) { result = (result * 0x10000000000002C5D) >> 64; } if (x & 0x2000 > 0) { result = (result * 0x1000000000000162E) >> 64; } if (x & 0x1000 > 0) { result = (result * 0x10000000000000B17) >> 64; } if (x & 0x800 > 0) { result = (result * 0x1000000000000058C) >> 64; } if (x & 0x400 > 0) { result = (result * 0x100000000000002C6) >> 64; } if (x & 0x200 > 0) { result = (result * 0x10000000000000163) >> 64; } if (x & 0x100 > 0) { result = (result * 0x100000000000000B1) >> 64; } if (x & 0x80 > 0) { result = (result * 0x10000000000000059) >> 64; } if (x & 0x40 > 0) { result = (result * 0x1000000000000002C) >> 64; } if (x & 0x20 > 0) { result = (result * 0x10000000000000016) >> 64; } if (x & 0x10 > 0) { result = (result * 0x1000000000000000B) >> 64; } if (x & 0x8 > 0) { result = (result * 0x10000000000000006) >> 64; } if (x & 0x4 > 0) { result = (result * 0x10000000000000003) >> 64; } if (x & 0x2 > 0) { result = (result * 0x10000000000000001) >> 64; } if (x & 0x1 > 0) { result = (result * 0x10000000000000001) >> 64; } // We're doing two things at the same time: // // 1. Multiply the result by 2^n + 1, where "2^n" is the integer part and the one is added to account for // the fact that we initially set the result to 0.5. This is accomplished by subtracting from 191 // rather than 192. // 2. Convert the result to the unsigned 60.18-decimal fixed-point format. // // This works because 2^(191-ip) = 2^ip / 2^191, where "ip" is the integer part "2^n". result *= SCALE; result >>= (191 - (x >> 64)); } } /// @notice Finds the zero-based index of the first one in the binary representation of x. /// @dev See the note on msb in the "Find First Set" Wikipedia article https://en.wikipedia.org/wiki/Find_first_set /// @param x The uint256 number for which to find the index of the most significant bit. /// @return msb The index of the most significant bit as an uint256. function mostSignificantBit(uint256 x) internal pure returns (uint256 msb) { if (x >= 2**128) { x >>= 128; msb += 128; } if (x >= 2**64) { x >>= 64; msb += 64; } if (x >= 2**32) { x >>= 32; msb += 32; } if (x >= 2**16) { x >>= 16; msb += 16; } if (x >= 2**8) { x >>= 8; msb += 8; } if (x >= 2**4) { x >>= 4; msb += 4; } if (x >= 2**2) { x >>= 2; msb += 2; } if (x >= 2**1) { // No need to shift x any more. msb += 1; } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv. /// /// Requirements: /// - The denominator cannot be zero. /// - The result must fit within uint256. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The multiplicand as an uint256. /// @param y The multiplier as an uint256. /// @param denominator The divisor as an uint256. /// @return result The result as an uint256. function mulDiv( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 result) { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { unchecked { result = prod0 / denominator; } return result; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (prod1 >= denominator) { revert PRBMath__MulDivOverflow(prod1, denominator); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. unchecked { // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 lpotdod = denominator & (~denominator + 1); assembly { // Divide denominator by lpotdod. denominator := div(denominator, lpotdod) // Divide [prod1 prod0] by lpotdod. prod0 := div(prod0, lpotdod) // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one. lpotdod := add(div(sub(0, lpotdod), lpotdod), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * lpotdod; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /// @notice Calculates floor(x*y÷1e18) with full precision. /// /// @dev Variant of "mulDiv" with constant folding, i.e. in which the denominator is always 1e18. Before returning the /// final result, we add 1 if (x * y) % SCALE >= HALF_SCALE. Without this, 6.6e-19 would be truncated to 0 instead of /// being rounded to 1e-18. See "Listing 6" and text above it at https://accu.org/index.php/journals/1717. /// /// Requirements: /// - The result must fit within uint256. /// /// Caveats: /// - The body is purposely left uncommented; see the NatSpec comments in "PRBMath.mulDiv" to understand how this works. /// - It is assumed that the result can never be type(uint256).max when x and y solve the following two equations: /// 1. x * y = type(uint256).max * SCALE /// 2. (x * y) % SCALE >= SCALE / 2 /// /// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number. /// @param y The multiplier as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function mulDivFixedPoint(uint256 x, uint256 y) internal pure returns (uint256 result) { uint256 prod0; uint256 prod1; assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } if (prod1 >= SCALE) { revert PRBMath__MulDivFixedPointOverflow(prod1); } uint256 remainder; uint256 roundUpUnit; assembly { remainder := mulmod(x, y, SCALE) roundUpUnit := gt(remainder, 499999999999999999) } if (prod1 == 0) { unchecked { result = (prod0 / SCALE) + roundUpUnit; return result; } } assembly { result := add( mul( or( div(sub(prod0, remainder), SCALE_LPOTD), mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, SCALE_LPOTD), SCALE_LPOTD), 1)) ), SCALE_INVERSE ), roundUpUnit ) } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev An extension of "mulDiv" for signed numbers. Works by computing the signs and the absolute values separately. /// /// Requirements: /// - None of the inputs can be type(int256).min. /// - The result must fit within int256. /// /// @param x The multiplicand as an int256. /// @param y The multiplier as an int256. /// @param denominator The divisor as an int256. /// @return result The result as an int256. function mulDivSigned( int256 x, int256 y, int256 denominator ) internal pure returns (int256 result) { if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) { revert PRBMath__MulDivSignedInputTooSmall(); } // Get hold of the absolute values of x, y and the denominator. uint256 ax; uint256 ay; uint256 ad; unchecked { ax = x < 0 ? uint256(-x) : uint256(x); ay = y < 0 ? uint256(-y) : uint256(y); ad = denominator < 0 ? uint256(-denominator) : uint256(denominator); } // Compute the absolute value of (x*y)÷denominator. The result must fit within int256. uint256 rAbs = mulDiv(ax, ay, ad); if (rAbs > uint256(type(int256).max)) { revert PRBMath__MulDivSignedOverflow(rAbs); } // Get the signs of x, y and the denominator. uint256 sx; uint256 sy; uint256 sd; assembly { sx := sgt(x, sub(0, 1)) sy := sgt(y, sub(0, 1)) sd := sgt(denominator, sub(0, 1)) } // XOR over sx, sy and sd. This is checking whether there are one or three negative signs in the inputs. // If yes, the result should be negative. result = sx ^ sy ^ sd == 0 ? -int256(rAbs) : int256(rAbs); } /// @notice Calculates the square root of x, rounding down. /// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The uint256 number for which to calculate the square root. /// @return result The result as an uint256. function sqrt(uint256 x) internal pure returns (uint256 result) { if (x == 0) { return 0; } // Set the initial guess to the least power of two that is greater than or equal to sqrt(x). uint256 xAux = uint256(x); result = 1; if (xAux >= 0x100000000000000000000000000000000) { xAux >>= 128; result <<= 64; } if (xAux >= 0x10000000000000000) { xAux >>= 64; result <<= 32; } if (xAux >= 0x100000000) { xAux >>= 32; result <<= 16; } if (xAux >= 0x10000) { xAux >>= 16; result <<= 8; } if (xAux >= 0x100) { xAux >>= 8; result <<= 4; } if (xAux >= 0x10) { xAux >>= 4; result <<= 2; } if (xAux >= 0x8) { result <<= 1; } // The operations can never overflow because the result is max 2^127 when it enters this block. unchecked { result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; // Seven iterations should be enough uint256 roundedDownResult = x / result; return result >= roundedDownResult ? roundedDownResult : result; } } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/interfaces/IFixable.sol"; import "./IvPoolSharesReceiver.sol"; import "../ctypes/ctypes.sol"; /// @title Exit Queue Interface /// @author mortimr @ Kiln /// @notice The exit queue stores exit requests until they are filled and claimable interface IvExitQueue is IFixable, IvPoolSharesReceiver { /// @notice Emitted when the stored Pool address is changed /// @param pool The new pool address event SetPool(address pool); /// @notice Emitted when the stored token uri image url is changed /// @param tokenUriImageUrl The new token uri image url event SetTokenUriImageUrl(string tokenUriImageUrl); /// @notice Emitted when the transfer enabled status is changed /// @param enabled The new transfer enabled status event SetTransferEnabled(bool enabled); /// @notice Emitted when the unclaimed funds buffer is changed /// @param unclaimedFunds The new unclaimed funds buffer event SetUnclaimedFunds(uint256 unclaimedFunds); /// @notice Emitted when ether was supplied to the vPool /// @param amount The amount of ETH supplied event SuppliedEther(uint256 amount); /// @notice Emitted when a ticket is created /// @param owner The address of the ticket owner /// @param idx The index of the ticket /// @param id The ID of the ticket /// @param ticket The ticket details event PrintedTicket(address indexed owner, uint32 idx, uint256 id, ctypes.Ticket ticket); /// @notice Emitted when a cask is created /// @param id The ID of the cask /// @param cask The cask details event ReceivedCask(uint32 id, ctypes.Cask cask); /// @notice Emitted when a ticket is claimed against a cask, can happen several times for the same ticket but different casks /// @param ticketId The ID of the ticket /// @param caskId The ID of the cask /// @param amountFilled The amount of shares filled /// @param amountEthFilled The amount of ETH filled /// @param unclaimedEth The amount of ETH that is added to the unclaimed buffer event FilledTicket( uint256 indexed ticketId, uint32 indexed caskId, uint128 amountFilled, uint256 amountEthFilled, uint256 unclaimedEth ); /// @notice Emitted when a ticket is "reminted" and its external id is modified /// @param oldTicketId The old ID of the ticket /// @param newTicketId The new ID of the ticket /// @param ticketIndex The index of the ticket event TicketIdUpdated(uint256 indexed oldTicketId, uint256 indexed newTicketId, uint32 indexed ticketIndex); /// @notice Emitted when a payment is made after a user performed a claim /// @param recipient The address of the recipient /// @param amount The amount of ETH paid event Payment(address indexed recipient, uint256 amount); /// @notice Transfer of tickets is disabled error TransferDisabled(); /// @notice The provided ticket ID is invalid /// @param id The ID of the ticket error InvalidTicketId(uint256 id); /// @notice The provided cask ID is invalid /// @param id The ID of the cask error InvalidCaskId(uint32 id); /// @notice The provided ticket IDs and cask IDs are not the same length error InvalidLengths(); /// @notice The ticket and cask are not associated /// @param ticketId The ID of the ticket /// @param caskId The ID of the cask error TicketNotMatchingCask(uint256 ticketId, uint32 caskId); /// @notice The claim transfer failed /// @param recipient The address of the recipient /// @param rdata The revert data error ClaimTransferFailed(address recipient, bytes rdata); enum ClaimStatus { CLAIMED, PARTIALLY_CLAIMED, SKIPPED } /// @notice Initializes the ExitQueue (proxy pattern) /// @param vpool The address of the associated vPool /// @param newTokenUriImageUrl The token uri image url function initialize(address vpool, string calldata newTokenUriImageUrl) external; /// @notice Returns the token uri image url /// @return The token uri image url function tokenUriImageUrl() external view returns (string memory); /// @notice Returns the transfer enabled status /// @return True if transfers are enabled function transferEnabled() external view returns (bool); /// @notice Returns the unclaimed funds buffer /// @return The unclaimed funds buffer function unclaimedFunds() external view returns (uint256); /// @notice Returns the id of the ticket based on the index /// @param idx The index of the ticket function ticketIdAtIndex(uint32 idx) external view returns (uint256); /// @notice Returns the details about the ticket with the provided ID /// @param id The ID of the ticket /// @return The ticket details function ticket(uint256 id) external view returns (ctypes.Ticket memory); /// @notice Returns the number of tickets /// @return The number of tickets function ticketCount() external view returns (uint256); /// @notice Returns the details about the cask with the provided ID /// @param id The ID of the cask /// @return The cask details function cask(uint32 id) external view returns (ctypes.Cask memory); /// @notice Returns the number of casks /// @return The number of casks function caskCount() external view returns (uint256); /// @notice Resolves the provided tickets to their associated casks or provide resolution error codes /// @dev TICKET_ID_OUT_OF_BOUNDS = -1; /// TICKET_ALREADY_CLAIMED = -2; /// TICKET_PENDING = -3; /// @param ticketIds The IDs of the tickets to resolve /// @return caskIdsOrErrors The IDs of the casks or error codes function resolve(uint256[] memory ticketIds) external view returns (int64[] memory caskIdsOrErrors); /// @notice Adds eth and creates a new cask /// @dev only callbacle by the vPool /// @param shares The amount of shares to cover with the provided eth function feed(uint256 shares) external payable; /// @notice Pulls eth from the unclaimed eth buffer /// @dev Only callable by the vPool /// @param max The maximum amount of eth to pull function pull(uint256 max) external; /// @notice Claims the provided tickets against their associated casks /// @dev To retrieve the list of casks, an off-chain resolve call should be performed /// @param ticketIds The IDs of the tickets to claim /// @param caskIds The IDs of the casks to claim against /// @param maxClaimDepth The maxiumum recursion depth for the claim, 0 for unlimited function claim(uint256[] calldata ticketIds, uint32[] calldata caskIds, uint16 maxClaimDepth) external returns (ClaimStatus[] memory statuses); /// @notice Sets the token uri image inside the returned token uri /// @param newTokenUriImageUrl The new token uri image url function setTokenUriImageUrl(string calldata newTokenUriImageUrl) external; /// @notice Enables or disables transfers of the tickets /// @param value True to allow transfers function setTransferEnabled(bool value) external; }
File 4 of 5: PluggableHatcher
// SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/Hatcher.sol"; import "utils.sol/libs/LibSanitize.sol"; import "../src/interfaces/IPluggableHatcher.sol"; /// @title Pluggable Hatcher /// @author mortimr @ Kiln /// @notice The PluggableHatcher extends the Hatcher and allows the nexus to spawn cubs contract PluggableHatcher is Hatcher, IPluggableHatcher { using LAddress for types.Address; using CAddress for address; /// @dev The nexus instance. /// @dev Slot: keccak256(bytes("pluggableHatcher.1.nexus")) - 1 types.Address internal constant $nexus = types.Address.wrap(0xf9a2bbc6604b460dea2b9e85ead19324d4c2b79c6ba1c0a5443b33d1c7d26559); /// @notice Prevents unauthorized calls modifier onlyNexus() { if (msg.sender != $nexus.get()) { revert LibErrors.Unauthorized(msg.sender, $nexus.get()); } _; } /// @param _implementation Address of the common implementation /// @param _admin Address administrating this contract /// @param _nexus Address of the nexus allowed to use plug constructor(address _implementation, address _admin, address _nexus) { LibSanitize.notZeroAddress(_nexus); _setImplementation(_implementation); _setAdmin(_admin); $nexus.set(_nexus); emit SetNexus(_nexus); } /// @inheritdoc IPluggableHatcher function nexus() external view returns (address) { return $nexus.get(); } /// @inheritdoc IPluggableHatcher function plug(bytes calldata cdata) external onlyNexus returns (address) { return _hatch(cdata); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./interfaces/IHatcher.sol"; import "./Cub.sol"; import "./Administrable.sol"; import "./Freezable.sol"; import "./libs/LibUint256.sol"; import "./libs/LibSanitize.sol"; import "./types/address.sol"; import "./types/uint256.sol"; import "./types/mapping.sol"; import "./types/array.sol"; import "./types/bool.sol"; /// @title Administrable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly. /// @dev In general, regarding the fixes, try to always perform atomic actions to apply them. /// @dev When using regular fixes, it's already the case. /// @dev When using global fixes, try to wrap multiple actions in one tx/bundle to create the global fix and apply it on required instances. /// @dev When removing a global fix, keep in mind that the action can be front runned and the fix that should be removed would be applied. /// @dev The hatcher can be frozen by the admin. Once frozen, no more upgrade, pausing or fixing is allowed. /// @dev If frozen and paused, a cub will be unpaused. /// @dev If frozen and pending fixes are still there, they will be applied to cubs that haven't applied them. /// @dev If frozen, pending fixes cannot be removed. /// @dev Initial progress and cub progress can get updated by the admin. This means a fix can be applied twice if progress is decreased. /// @notice This contract provides all the utilities to handle the administration and its transfer abstract contract Hatcher is Administrable, Freezable, IHatcher { using LAddress for types.Address; using LUint256 for types.Uint256; using LMapping for types.Mapping; using LArray for types.Array; using LBool for types.Bool; using CAddress for address; using CUint256 for uint256; using CBool for bool; /// @dev Unstructured Storage Helper for hatcher.pauser. /// @dev Holds the pauser address. /// @dev Slot: keccak256(bytes("hatcher.pauser")) - 1 types.Address internal constant $pauser = types.Address.wrap(0x67ad2ba345683ea58e6dcc49f12611548bc3a5b2c8c753edc1878aa0a76c3ce2); /// @dev Unstructured Storage Helper for hatcher.implementation. /// @dev Holds the common implementation used by all the cubs. /// @dev Slot: keccak256(bytes("hatcher.implementation")) - 1 types.Address internal constant $implementation = types.Address.wrap(0x5822215992e9fc50486d8256024d96ad28d5ca5cb787840aef51159121dccd9d); /// @dev Unstructured Storage Helper for hatcher.initialProgress. /// @dev Holds the initial progress value given to all new cubs. /// @dev Supersedes the progress of old cubs if the value is higher than their progress. /// @dev Slot: keccak256(bytes("hatcher.initialProgress")) - 1 types.Uint256 internal constant $initialProgress = types.Uint256.wrap(0x4a267ea82c1f4624b3dc08ad19614228bbdeee20d07eb9966d67c16d39550d77); /// @dev Unstructured Storage Helper for hatcher.fixProgresses. /// @dev Holds the value of the fix progress of every cub. /// @dev Type: mapping (address => uint256) /// @dev Slot: keccak256(bytes("hatcher.fixProgresses")) - 1 types.Mapping internal constant $fixProgresses = types.Mapping.wrap(0xa7208bf4db7440ac9388b234d45a5b207976f0fc12d31bf9eaa80e4e2fc0d57c); /// @dev Unstructured Storage Helper for hatcher.pauseStatus. /// @dev Holds the pause status of every cub. /// @dev Type: mapping (address => bool) /// @dev Slot: keccak256(bytes("hatcher.pauseStatus")) - 1 types.Mapping internal constant $pauseStatus = types.Mapping.wrap(0xd0ad769ee84b03ff353d2cb4c134ab25db1f330b56357f28eadc5b28c2f88991); /// @dev Unstructured Storage Helper for hatcher.globalPauseStatus. /// @dev Holds the global pause status. /// @dev Slot: keccak256(bytes("hatcher.globalPauseStatus")) - 1 types.Bool internal constant $globalPauseStatus = types.Bool.wrap(0x798f8d9ad9ed68e65653cd13b4f27162f01222155b56622ae81337e4888e20c0); /// @dev Unstructured Storage Helper for hatcher.fixes. /// @dev Holds the array of global fixes. /// @dev Slot: keccak256(bytes("hatcher.fixes")) - 1 types.Array internal constant $fixes = types.Array.wrap(0xa8612761e880b1989e2ad0bb2c51004fad089f897b1cd8dc3dbfeae33493df55); /// @dev Unstructured Storage Helper for hatcher.initialProgress. /// @dev Holds the create2 salt. /// @dev Slot: keccak256(bytes("hatcher.creationSalt")) - 1 types.Uint256 internal constant $creationSalt = types.Uint256.wrap(0x7b4670a3a88a40c4de314967df154b504cc215cbd280a064c677342c49c2759d); /// @dev Only allows admin or pauser to perform the call. modifier onlyAdminOrPauser() { if (msg.sender != _getAdmin() && msg.sender != $pauser.get()) { revert LibErrors.Unauthorized(msg.sender, address(0)); } _; } /// @inheritdoc IHatcher function implementation() external view returns (address) { return $implementation.get(); } /// @inheritdoc IHatcher // slither-disable-next-line timestamp function status(address cub) external view returns (address, bool, bool) { return ( $implementation.get(), $fixProgresses.get()[cub.k()] < $fixes.toAddressA().length, ($globalPauseStatus.get() || $pauseStatus.get()[cub.k()].toBool()) && !_isFrozen() ); } /// @inheritdoc IHatcher function initialProgress() external view returns (uint256) { return $initialProgress.get(); } /// @inheritdoc IHatcher function progress(address cub) external view returns (uint256) { return $fixProgresses.get()[cub.k()]; } /// @inheritdoc IHatcher function globalPaused() external view returns (bool) { return $globalPauseStatus.get(); } /// @inheritdoc IHatcher function paused(address cub) external view returns (bool) { return $pauseStatus.get()[cub.k()].toBool(); } /// @inheritdoc IHatcher function pauser() external view returns (address) { return $pauser.get(); } /// @inheritdoc IHatcher function fixes(address cub) external view returns (address[] memory) { uint256 currentProgress = $fixProgresses.get()[cub.k()]; uint256 rawFixCount = $fixes.toAddressA().length; uint256 fixCount = rawFixCount - LibUint256.min(currentProgress, rawFixCount); address[] memory forwardedFixes = new address[](fixCount); for (uint256 idx = 0; idx < fixCount;) { forwardedFixes[idx] = $fixes.toAddressA()[idx + currentProgress]; unchecked { ++idx; } } return forwardedFixes; } /// @inheritdoc IHatcher /// @dev This method is not view because it reads the fixes from storage. function globalFixes() external pure returns (address[] memory) { return $fixes.toAddressA(); } /// @inheritdoc IHatcher function nextHatch() external view returns (address) { return _nextHatch(); } /// @inheritdoc IHatcher function frozen() external view returns (bool) { return _isFrozen(); } /// @inheritdoc IHatcher function freezeTime() external view returns (uint256) { return _freezeTime(); } /// @inheritdoc IHatcher function hatch(bytes calldata cdata) external virtual onlyAdmin returns (address) { return _hatch(cdata); } /// @inheritdoc IHatcher function hatch() external virtual onlyAdmin returns (address) { return _hatch(""); } /// @inheritdoc IHatcher function commitFixes() external { address cub = msg.sender; uint256 newProgress = $fixes.toAddressA().length; $fixProgresses.get()[cub.k()] = newProgress; emit CommittedFixes(cub, newProgress); } /// @inheritdoc IHatcher function setPauser(address newPauser) external onlyAdmin { _setPauser(newPauser); } /// @inheritdoc IHatcher // slither-disable-next-line reentrancy-events,calls-loop function applyFixToCubs(address fixer, address[] calldata cubs) external notFrozen onlyAdmin { LibSanitize.notZeroAddress(fixer); uint256 cubCount = cubs.length; for (uint256 idx = 0; idx < cubCount;) { LibSanitize.notZeroAddress(cubs[idx]); Cub(payable(cubs[idx])).applyFix(fixer); emit AppliedFix(cubs[idx], fixer); unchecked { ++idx; } } } /// @inheritdoc IHatcher // slither-disable-next-line reentrancy-events,calls-loop function applyFixesToCub(address cub, address[] calldata fixers) external notFrozen onlyAdmin { LibSanitize.notZeroAddress(cub); uint256 fixCount = fixers.length; for (uint256 idx = 0; idx < fixCount;) { LibSanitize.notZeroAddress(fixers[idx]); Cub(payable(cub)).applyFix(fixers[idx]); emit AppliedFix(cub, fixers[idx]); unchecked { ++idx; } } } /// @inheritdoc IHatcher function registerGlobalFix(address fixer) external notFrozen onlyAdmin { LibSanitize.notZeroAddress(fixer); $fixes.toAddressA().push(fixer); emit RegisteredGlobalFix(fixer, $fixes.toAddressA().length - 1); } /// @inheritdoc IHatcher function deleteGlobalFix(uint256 index) external notFrozen onlyAdmin { $fixes.toAddressA()[index] = address(0); emit DeletedGlobalFix(index); } /// @inheritdoc IHatcher function upgradeTo(address newImplementation) external notFrozen onlyAdmin { _setImplementation(newImplementation); } /// @inheritdoc IHatcher function upgradeToAndChangeInitialProgress(address newImplementation, uint256 initialProgress_) external notFrozen onlyAdmin { _setInitialProgress(initialProgress_); _setImplementation(newImplementation); } /// @inheritdoc IHatcher function setInitialProgress(uint256 initialProgress_) external notFrozen onlyAdmin { _setInitialProgress(initialProgress_); } /// @inheritdoc IHatcher function setCubProgress(address cub, uint256 newProgress) external notFrozen onlyAdmin { $fixProgresses.get()[cub.k()] = newProgress; emit CommittedFixes(cub, newProgress); } /// @inheritdoc IHatcher function pauseCubs(address[] calldata cubs) external notFrozen onlyAdminOrPauser { for (uint256 idx = 0; idx < cubs.length;) { LibSanitize.notZeroAddress(cubs[idx]); _pause(cubs[idx]); unchecked { ++idx; } } } /// @inheritdoc IHatcher function unpauseCubs(address[] calldata cubs) external notFrozen onlyAdmin { for (uint256 idx = 0; idx < cubs.length;) { LibSanitize.notZeroAddress(cubs[idx]); _unpause(cubs[idx]); unchecked { ++idx; } } } /// @inheritdoc IHatcher function globalPause() external notFrozen onlyAdminOrPauser { $globalPauseStatus.set(true); emit GlobalPause(); } /// @inheritdoc IHatcher function globalUnpause() external notFrozen onlyAdmin { $globalPauseStatus.set(false); emit GlobalUnpause(); } /// @inheritdoc IHatcher function freeze(uint256 freezeTimeout) external { _freeze(freezeTimeout); } /// @inheritdoc IHatcher function cancelFreeze() external { _cancelFreeze(); } /// @dev Internal utility to set the pauser address. /// @param newPauser Address of the new pauser function _setPauser(address newPauser) internal { $pauser.set(newPauser); emit SetPauser(newPauser); } /// @dev Internal utility to change the common implementation. /// @dev Reverts if the new implementation is not a contract. /// @param newImplementation Address of the new implementation function _setImplementation(address newImplementation) internal { LibSanitize.notZeroAddress(newImplementation); if (newImplementation.code.length == 0) { revert ImplementationNotAContract(newImplementation); } $implementation.set(newImplementation); emit Upgraded(newImplementation); } /// @dev Internal utility to retrieve the address of the next deployed Cub. /// @return Address of the next cub // slither-disable-next-line too-many-digits function _nextHatch() internal view returns (address) { return address( uint160( uint256( keccak256( abi.encodePacked( hex"ff", address(this), bytes32($creationSalt.get()), keccak256(type(Cub).creationCode) ) ) ) ) ); } /// @dev Internal utility to create a new Cub. /// @dev The provided cdata is used to perform an atomic call upon contract creation. /// @param cdata The calldata to use for the atomic creation call // slither-disable-next-line reentrancy-events function _hatch(bytes memory cdata) internal returns (address cub) { uint256 salt = $creationSalt.get(); $creationSalt.set(salt + 1); cub = address((new Cub){salt: bytes32(salt)}()); uint256 currentInitialProgress = $initialProgress.get(); if (currentInitialProgress > 0) { $fixProgresses.get()[cub.k()] = currentInitialProgress; } Cub(payable(cub)).___initializeCub(address(this), cdata); emit Hatched(cub, cdata); } /// @dev Internal utility to pause a cub. /// @param cub The cub to pause function _pause(address cub) internal { $pauseStatus.get()[cub.k()] = true.v(); emit Pause(cub); } /// @dev Internal utility to unpause a cub. /// @param cub The cub to unpause function _unpause(address cub) internal { $pauseStatus.get()[cub.k()] = false.v(); emit Unpause(cub); } /// @dev Internal utility to set the initial cub progress. /// @dev This value defines where the new cubs should start applying fixes from the global fix array. /// @dev This value supersedes existing cub progresses if the progress is lower than this value. /// @param initialProgress_ New initial progress function _setInitialProgress(uint256 initialProgress_) internal { $initialProgress.set(initialProgress_); emit SetInitialProgress(initialProgress_); } /// @dev Internal utility to retrieve the address of the freezer. /// @return Address of the freezer function _getFreezer() internal view override returns (address) { return _getAdmin(); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./LibErrors.sol"; import "./LibConstant.sol"; /// @title Lib Sanitize /// @dev This library helps sanitizing inputs. library LibSanitize { /// @dev Internal utility to sanitize an address and ensure its value is not 0. /// @param addressValue The address to verify // slither-disable-next-line dead-code function notZeroAddress(address addressValue) internal pure { if (addressValue == address(0)) { revert LibErrors.InvalidZeroAddress(); } } /// @dev Internal utility to sanitize an uint256 value and ensure its value is not 0. /// @param value The value to verify // slither-disable-next-line dead-code function notNullValue(uint256 value) internal pure { if (value == 0) { revert LibErrors.InvalidNullValue(); } } /// @dev Internal utility to sanitize a bps value and ensure it's <= 100%. /// @param value The bps value to verify // slither-disable-next-line dead-code function notInvalidBps(uint256 value) internal pure { if (value > LibConstant.BASIS_POINTS_MAX) { revert LibErrors.InvalidBPSValue(); } } /// @dev Internal utility to sanitize a string value and ensure it's not empty. /// @param stringValue The string value to verify // slither-disable-next-line dead-code function notEmptyString(string memory stringValue) internal pure { if (bytes(stringValue).length == 0) { revert LibErrors.InvalidEmptyString(); } } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; /// @title Pluggable Hatcher Interface /// @author mortimr @ Kiln /// @notice The PluggableHatcher extends the Hatcher and allows the nexus to spawn cubs interface IPluggableHatcher { /// @notice Emitted when the stored Nexus address is changed /// @param nexus The new nexus address event SetNexus(address nexus); /// @notice Method allowing the Nexus to hatch a new cub /// @param cdata The calldata to provide to the hatch method /// @return The address of the new cub function plug(bytes calldata cdata) external returns (address); /// @notice Retrieve the configured nexus address /// @return The nexus address function nexus() external view returns (address); } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "openzeppelin-contracts/proxy/beacon/IBeacon.sol"; /// @title Hatcher Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Hatcher can deploy, upgrade, fix and pause a set of instances called cubs. /// All cubs point to the same coomon implementation. interface IHatcher is IBeacon { /// @notice Emitted when the system is globally paused. event GlobalPause(); /// @notice Emitted when the system is globally unpaused. event GlobalUnpause(); /// @notice Emitted when a specific cub is paused. /// @param cub Address of the cub being paused event Pause(address cub); /// @notice Emitted when a specific cub is unpaused. /// @param cub Address of the cub being unpaused event Unpause(address cub); /// @notice Emitted when a global fix is removed. /// @param index Index of the global fix being removed event DeletedGlobalFix(uint256 index); /// @notice Emitted when a cub has properly applied a fix. /// @param cub Address of the cub that applied the fix /// @param fix Address of the fix was applied event AppliedFix(address cub, address fix); /// @notice Emitted the common implementation is updated. /// @param implementation New common implementation address event Upgraded(address indexed implementation); /// @notice Emitted a new cub is hatched. /// @param cub Address of the new instance /// @param cdata Calldata used to perform the atomic first call event Hatched(address indexed cub, bytes cdata); /// @notice Emitted a the initial progress has been changed. /// @param initialProgress New initial progress value event SetInitialProgress(uint256 initialProgress); /// @notice Emitted a new pauser is set. /// @param pauser Address of the new pauser event SetPauser(address pauser); /// @notice Emitted a cub committed some global fixes. /// @param cub Address of the cub that applied the global fixes /// @param progress New cub progress event CommittedFixes(address cub, uint256 progress); /// @notice Emitted a global fix is registered. /// @param fix Address of the new global fix /// @param index Index of the new global fix in the global fix array event RegisteredGlobalFix(address fix, uint256 index); /// @notice The provided implementation is not a smart contract. /// @param implementation The provided implementation error ImplementationNotAContract(address implementation); /// @notice Retrieve the common implementation. /// @return implementationAddress Address of the common implementation function implementation() external view returns (address implementationAddress); /// @notice Retrieve cub status details. /// @param cub The address of the cub to fetch the status of /// @return implementationAddress The current implementation address to use /// @return hasFixes True if there are fixes to apply /// @return isPaused True if the system is paused globally or the calling cub is paused function status(address cub) external view returns (address implementationAddress, bool hasFixes, bool isPaused); /// @notice Retrieve the initial progress. /// @dev This value is the starting progress value for all new cubs /// @return currentInitialProgress The initial progress function initialProgress() external view returns (uint256 currentInitialProgress); /// @notice Retrieve the current progress of a specific cub. /// @param cub Address of the cub /// @return currentProgress The current progress of the cub function progress(address cub) external view returns (uint256 currentProgress); /// @notice Retrieve the global pause status. /// @return isGlobalPaused True if globally paused function globalPaused() external view returns (bool isGlobalPaused); /// @notice Retrieve a cub pause status. /// @param cub Address of the cub /// @return isPaused True if paused function paused(address cub) external view returns (bool isPaused); /// @notice Retrieve the address of the pauser. function pauser() external view returns (address); /// @notice Retrieve a cub's global fixes that need to be applied, taking its progress into account. /// @param cub Address of the cub /// @return fixesAddresses An array of addresses that implement fixes function fixes(address cub) external view returns (address[] memory fixesAddresses); /// @notice Retrieve the raw list of global fixes. /// @return globalFixesAddresses An array of addresses that implement the global fixes function globalFixes() external view returns (address[] memory globalFixesAddresses); /// @notice Retrieve the address of the next hatched cub. /// @return nextHatchedCub The address of the next cub function nextHatch() external view returns (address nextHatchedCub); /// @notice Retrieve the freeze status. /// @return True if frozen function frozen() external view returns (bool); /// @notice Retrieve the timestamp when the freeze happens. /// @return The freeze timestamp function freezeTime() external view returns (uint256); /// @notice Creates a new cub. /// @param cdata The calldata to use for the initial atomic call /// @return cubAddress The address of the new cub function hatch(bytes calldata cdata) external returns (address cubAddress); /// @notice Creates a new cub, without calldata. /// @return cubAddress The address of the new cub function hatch() external returns (address cubAddress); /// @notice Sets the progress of the caller to the current global fixes array length. function commitFixes() external; /// @notice Sets the address of the pauser. /// @param newPauser Address of the new pauser function setPauser(address newPauser) external; /// @notice Apply a fix to several cubs. /// @param fixer Fixer contract implementing the fix /// @param cubs List of cubs to apply the fix on function applyFixToCubs(address fixer, address[] calldata cubs) external; /// @notice Apply several fixes to one cub. /// @param cub The cub to apply the fixes on /// @param fixers List of fixer contracts implementing the fixes function applyFixesToCub(address cub, address[] calldata fixers) external; /// @notice Register a new global fix for cubs to call asynchronously. /// @param fixer Address of the fixer implementing the fix function registerGlobalFix(address fixer) external; /// @notice Deletes a global fix from the array. /// @param index Index of the global fix to remove function deleteGlobalFix(uint256 index) external; /// @notice Upgrades the common implementation address. /// @param newImplementation Address of the new common implementation function upgradeTo(address newImplementation) external; /// @notice Upgrades the common implementation address and the initial progress value. /// @param newImplementation Address of the new common implementation /// @param initialProgress_ The new initial progress value function upgradeToAndChangeInitialProgress(address newImplementation, uint256 initialProgress_) external; /// @notice Sets the initial progress value. /// @param initialProgress_ The new initial progress value function setInitialProgress(uint256 initialProgress_) external; /// @notice Sets the progress of a cub. /// @param cub Address of the cub /// @param newProgress New progress value function setCubProgress(address cub, uint256 newProgress) external; /// @notice Pauses a set of cubs. /// @param cubs List of cubs to pause function pauseCubs(address[] calldata cubs) external; /// @notice Unpauses a set of cubs. /// @param cubs List of cubs to unpause function unpauseCubs(address[] calldata cubs) external; /// @notice Pauses all the cubs of the system. function globalPause() external; /// @notice Unpauses all the cubs of the system. /// @dev If a cub was specifically paused, this method won't unpause it function globalUnpause() external; /// @notice Sets the freeze timestamp. /// @param freezeTimeout The timeout to add to current timestamp before freeze happens function freeze(uint256 freezeTimeout) external; /// @notice Cancels the freezing procedure. function cancelFreeze() external; } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "openzeppelin-contracts/proxy/beacon/BeaconProxy.sol"; import "./interfaces/IFixer.sol"; import "./interfaces/IHatcher.sol"; import "./interfaces/ICub.sol"; /// @title Cub /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The cub is controlled by a Hatcher in charge of providing its status details and implementation address. contract Cub is Proxy, ERC1967Upgrade, ICub { /// @notice Initializer to not rely on the constructor. /// @param beacon The address of the beacon to pull its info from /// @param data The calldata to add to the initial call, if any // slither-disable-next-line naming-convention function ___initializeCub(address beacon, bytes memory data) external { if (_getBeacon() != address(0)) { revert CubAlreadyInitialized(); } _upgradeBeaconToAndCall(beacon, data, false); } /// @dev Internal utility to retrieve the implementation from the beacon. /// @return The implementation address // slither-disable-next-line dead-code function _implementation() internal view virtual override returns (address) { return IBeacon(_getBeacon()).implementation(); } /// @dev Prevents unauthorized calls. /// @dev This will make the method transparent, forcing unauthorized callers into the fallback. modifier onlyBeacon() { if (msg.sender != _getBeacon()) { _fallback(); } else { _; } } /// @dev Prevents unauthorized calls. /// @dev This will make the method transparent, forcing unauthorized callers into the fallback. modifier onlyMe() { if (msg.sender != address(this)) { _fallback(); } else { _; } } /// @inheritdoc ICub // slither-disable-next-line reentrancy-events function appliedFixes(address[] memory fixers) public onlyMe { emit AppliedFixes(fixers); } /// @inheritdoc ICub function applyFix(address fixer) external onlyBeacon { _applyFix(fixer); } /// @dev Retrieve the list of fixes for this cub from the hatcher. /// @param beacon Address of the hatcher acting as a beacon /// @return List of fixes to apply function _fixes(address beacon) internal view returns (address[] memory) { return IHatcher(beacon).fixes(address(this)); } /// @dev Retrieve the status for this cub from the hatcher. /// @param beacon Address of the hatcher acting as a beacon /// @return First value is true if fixes are pending, second value is true if cub is paused function _status(address beacon) internal view returns (address, bool, bool) { return IHatcher(beacon).status(address(this)); } /// @dev Commits fixes to the hatcher. /// @param beacon Address of the hatcher acting as a beacon function _commit(address beacon) internal { IHatcher(beacon).commitFixes(); } /// @dev Fetches the current cub status and acts accordingly. /// @param beacon Address of the hatcher acting as a beacon function _fix(address beacon) internal returns (address) { (address implementation, bool hasFixes, bool isPaused) = _status(beacon); if (isPaused && msg.sender != address(0)) { revert CalledWhenPaused(msg.sender); } if (hasFixes) { bool isStaticCall = false; address[] memory fixes = _fixes(beacon); // This is a trick to check if the current execution context // allows state modifications try this.appliedFixes(fixes) {} catch { isStaticCall = true; } // if we properly emitted AppliedFixes, we are not in a view or pure call // we can then apply fixes if (!isStaticCall) { for (uint256 idx = 0; idx < fixes.length;) { if (fixes[idx] != address(0)) { _applyFix(fixes[idx]); } unchecked { ++idx; } } _commit(beacon); } } return implementation; } /// @dev Applies the given fix, and reverts in case of error. /// @param fixer Address that implements the fix // slither-disable-next-line controlled-delegatecall,delegatecall-loop,low-level-calls function _applyFix(address fixer) internal { (bool success, bytes memory rdata) = fixer.delegatecall(abi.encodeCall(IFixer.fix, ())); if (!success) { revert FixDelegateCallError(fixer, rdata); } (success) = abi.decode(rdata, (bool)); if (!success) { revert FixCallError(fixer); } } /// @dev Fallback method that ends up forwarding calls as delegatecalls to the implementation. function _fallback() internal override(Proxy) { _beforeFallback(); address beacon = _getBeacon(); address implementation = _fix(beacon); _delegate(implementation); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./libs/LibSanitize.sol"; import "./types/address.sol"; import "./interfaces/IAdministrable.sol"; /// @title Administrable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contract provides all the utilities to handle the administration and its transfer. abstract contract Administrable is IAdministrable { using LAddress for types.Address; /// @dev The admin address in storage. /// @dev Slot: keccak256(bytes("administrable.admin")) - 1 types.Address internal constant $admin = types.Address.wrap(0x927a17e5ea75d9461748062a2652f4d3698a628896c9832f8488fa0d2846af09); /// @dev The pending admin address in storage. /// @dev Slot: keccak256(bytes("administrable.pendingAdmin")) - 1 types.Address internal constant $pendingAdmin = types.Address.wrap(0x3c1eebcc225c6cc7f5f8765767af6eff617b4139dc3624923a2db67dbca7b68e); /// @dev This modifier ensures that only the admin is able to call the method. modifier onlyAdmin() { if (msg.sender != _getAdmin()) { revert LibErrors.Unauthorized(msg.sender, _getAdmin()); } _; } /// @dev This modifier ensures that only the pending admin is able to call the method. modifier onlyPendingAdmin() { if (msg.sender != _getPendingAdmin()) { revert LibErrors.Unauthorized(msg.sender, _getPendingAdmin()); } _; } /// @inheritdoc IAdministrable function admin() external view returns (address) { return _getAdmin(); } /// @inheritdoc IAdministrable function pendingAdmin() external view returns (address) { return _getPendingAdmin(); } /// @notice Propose a new admin. /// @dev Only callable by the admin. /// @param newAdmin The new admin to propose function transferAdmin(address newAdmin) external onlyAdmin { _setPendingAdmin(newAdmin); } /// @notice Accept an admin transfer. /// @dev Only callable by the pending admin. function acceptAdmin() external onlyPendingAdmin { _setAdmin(msg.sender); _setPendingAdmin(address(0)); } /// @dev Retrieve the admin address. /// @return The admin address function _getAdmin() internal view returns (address) { return $admin.get(); } /// @dev Change the admin address. /// @param newAdmin The new admin address function _setAdmin(address newAdmin) internal { LibSanitize.notZeroAddress(newAdmin); emit SetAdmin(newAdmin); $admin.set(newAdmin); } /// @dev Retrieve the pending admin address. /// @return The pending admin address function _getPendingAdmin() internal view returns (address) { return $pendingAdmin.get(); } /// @dev Change the pending admin address. /// @param newPendingAdmin The new pending admin address function _setPendingAdmin(address newPendingAdmin) internal { emit SetPendingAdmin(newPendingAdmin); $pendingAdmin.set(newPendingAdmin); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; // For some unexplainable and mysterious reason, adding this line would make slither crash // This is the reason why we are not using our own unstructured storage libs in this contract // (while the libs work properly in a lot of contracts without slither having any issue with it) // import "./types/uint256.sol"; import "./libs/LibErrors.sol"; import "./libs/LibConstant.sol"; import "openzeppelin-contracts/utils/StorageSlot.sol"; /// @title Freezable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Freezable contract is used to add a freezing capability to admin related actions. /// The goal would be to ossify an implementation after a certain amount of time. // slither-disable-next-line unimplemented-functions abstract contract Freezable { /// @notice Thrown when a call happened while it was forbidden when frozen. error Frozen(); /// @notice Thrown when the provided timeout value is lower than 100 days. /// @param providedValue The user provided value /// @param minimumValue The minimum allowed value error FreezeTimeoutTooLow(uint256 providedValue, uint256 minimumValue); /// @notice Emitted when the freeze timeout is changed. /// @param freezeTime The timestamp after which the contract will be frozen event SetFreezeTime(uint256 freezeTime); /// @dev This is the keccak-256 hash of "freezable.freeze_timestamp" subtracted by 1. bytes32 private constant _FREEZE_TIMESTAMP_SLOT = 0x04b06dd5becaad633b58f99e01f1e05103eff5a573d10d18c9baf1bc4e6bfd3a; /// @dev Only callable by the freezer account. modifier onlyFreezer() { _onlyFreezer(); _; } /// @dev Only callable when not frozen. modifier notFrozen() { _notFrozen(); _; } /// @dev Override and set it to return the address to consider as the freezer. /// @return The freezer address // slither-disable-next-line dead-code function _getFreezer() internal view virtual returns (address); /// @dev Retrieve the freeze status. /// @return True if contract is frozen // slither-disable-next-line dead-code,timestamp function _isFrozen() internal view returns (bool) { uint256 freezeTime_ = _freezeTime(); return (freezeTime_ > 0 && block.timestamp >= freezeTime_); } /// @dev Retrieve the freeze timestamp. /// @return The freeze timestamp // slither-disable-next-line dead-code function _freezeTime() internal view returns (uint256) { return StorageSlot.getUint256Slot(_FREEZE_TIMESTAMP_SLOT).value; } /// @dev Internal utility to set the freeze timestamp. /// @param freezeTime The new freeze timestamp // slither-disable-next-line dead-code function _setFreezeTime(uint256 freezeTime) internal { StorageSlot.getUint256Slot(_FREEZE_TIMESTAMP_SLOT).value = freezeTime; emit SetFreezeTime(freezeTime); } /// @dev Internal utility to revert if caller is not freezer. // slither-disable-next-line dead-code function _onlyFreezer() internal view { if (msg.sender != _getFreezer()) { revert LibErrors.Unauthorized(msg.sender, _getFreezer()); } } /// @dev Internal utility to revert if contract is frozen. // slither-disable-next-line dead-code function _notFrozen() internal view { if (_isFrozen()) { revert Frozen(); } } /// @dev Internal utility to start the freezing procedure. /// @param freezeTimeout Timeout to add to current timestamp to define freeze timestamp // slither-disable-next-line dead-code function _freeze(uint256 freezeTimeout) internal { _notFrozen(); _onlyFreezer(); if (freezeTimeout < LibConstant.MINIMUM_FREEZE_TIMEOUT) { revert FreezeTimeoutTooLow(freezeTimeout, LibConstant.MINIMUM_FREEZE_TIMEOUT); } // overflow would revert uint256 now_ = block.timestamp; uint256 freezeTime_ = now_ + freezeTimeout; _setFreezeTime(freezeTime_); } /// @dev Internal utility to cancel the freezing procedure. // slither-disable-next-line dead-code function _cancelFreeze() internal { _notFrozen(); _onlyFreezer(); _setFreezeTime(0); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "prb-math/PRBMath.sol"; library LibUint256 { // slither-disable-next-line dead-code function min(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly // slither-disable-next-line assembly assembly { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @custom:author Vectorized/solady#58681e79de23082fd3881a76022e0842f5c08db8 // slither-disable-next-line dead-code function max(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly // slither-disable-next-line assembly assembly { z := xor(x, mul(xor(x, y), gt(y, x))) } } // slither-disable-next-line dead-code function mulDiv(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) { return PRBMath.mulDiv(a, b, c); } // slither-disable-next-line dead-code function ceil(uint256 num, uint256 den) internal pure returns (uint256) { return (num / den) + (num % den > 0 ? 1 : 0); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; /// @notice Library Address - Address slot utilities. library LAddress { // slither-disable-next-line dead-code, assembly function get(types.Address position) internal view returns (address data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Address position, address data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Address position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CAddress { // slither-disable-next-line dead-code function toUint256(address val) internal pure returns (uint256) { return uint256(uint160(val)); } // slither-disable-next-line dead-code function toBytes32(address val) internal pure returns (bytes32) { return bytes32(uint256(uint160(val))); } // slither-disable-next-line dead-code function toBool(address val) internal pure returns (bool converted) { // slither-disable-next-line assembly assembly { converted := gt(val, 0) } } /// @notice This method should be used to convert an address to a uint256 when used as a key in a mapping. // slither-disable-next-line dead-code function k(address val) internal pure returns (uint256) { return toUint256(val); } /// @notice This method should be used to convert an address to a uint256 when used as a value in a mapping. // slither-disable-next-line dead-code function v(address val) internal pure returns (uint256) { return toUint256(val); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LUint256 { // slither-disable-next-line dead-code function get(types.Uint256 position) internal view returns (uint256 data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Uint256 position, uint256 data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Uint256 position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CUint256 { // slither-disable-next-line dead-code function toBytes32(uint256 val) internal pure returns (bytes32) { return bytes32(val); } // slither-disable-next-line dead-code function toAddress(uint256 val) internal pure returns (address) { return address(uint160(val)); } // slither-disable-next-line dead-code function toBool(uint256 val) internal pure returns (bool) { return (val & 1) == 1; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LMapping { // slither-disable-next-line dead-code function get(types.Mapping position) internal pure returns (mapping(uint256 => uint256) storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LArray { // slither-disable-next-line dead-code function toUintA(types.Array position) internal pure returns (uint256[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function toAddressA(types.Array position) internal pure returns (address[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function toBoolA(types.Array position) internal pure returns (bool[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function toBytes32A(types.Array position) internal pure returns (bytes32[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } // slither-disable-next-line dead-code function del(types.Array position) internal { // slither-disable-next-line assembly assembly { let len := sload(position) if len { // clear the length slot sstore(position, 0) // calculate the starting slot of the array elements in storage mstore(0, position) let startPtr := keccak256(0, 0x20) for {} len {} { len := sub(len, 1) sstore(add(startPtr, len), 0) } } } } /// @dev This delete can be used if and only if we only want to clear the length of the array. /// Doing so will create an array that behaves like an empty array in solidity. /// It can have advantages if we often rewrite to the same slots of the array. /// Prefer using `del` if you don't know what you're doing. // slither-disable-next-line dead-code function dangerousDirtyDel(types.Array position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LBool { // slither-disable-next-line dead-code function get(types.Bool position) internal view returns (bool data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Bool position, bool data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Bool position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CBool { // slither-disable-next-line dead-code function toBytes32(bool val) internal pure returns (bytes32) { return bytes32(toUint256(val)); } // slither-disable-next-line dead-code function toAddress(bool val) internal pure returns (address) { return address(uint160(toUint256(val))); } // slither-disable-next-line dead-code function toUint256(bool val) internal pure returns (uint256 converted) { // slither-disable-next-line assembly assembly { converted := iszero(iszero(val)) } } /// @dev This method should be used to convert a bool to a uint256 when used as a key in a mapping. // slither-disable-next-line dead-code function k(bool val) internal pure returns (uint256) { return toUint256(val); } /// @dev This method should be used to convert a bool to a uint256 when used as a value in a mapping. // slither-disable-next-line dead-code function v(bool val) internal pure returns (uint256) { return toUint256(val); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibErrors { error Unauthorized(address account, address expected); error InvalidZeroAddress(); error InvalidNullValue(); error InvalidBPSValue(); error InvalidEmptyString(); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibConstant { /// @dev The basis points value representing 100%. uint256 internal constant BASIS_POINTS_MAX = 10_000; /// @dev The size of a deposit to activate a validator. uint256 internal constant DEPOSIT_SIZE = 32 ether; /// @dev The minimum freeze timeout before freeze is active. uint256 internal constant MINIMUM_FREEZE_TIMEOUT = 100 days; /// @dev Address used to represent ETH when an address is required to identify an asset. address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; } // 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 (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol) pragma solidity ^0.8.0; import "./IBeacon.sol"; import "../Proxy.sol"; import "../ERC1967/ERC1967Upgrade.sol"; /** * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. * * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't * conflict with the storage layout of the implementation behind the proxy. * * _Available since v3.4._ */ contract BeaconProxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the proxy with `beacon`. * * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity * constructor. * * Requirements: * * - `beacon` must be a contract with the interface {IBeacon}. */ constructor(address beacon, bytes memory data) payable { _upgradeBeaconToAndCall(beacon, data, false); } /** * @dev Returns the current beacon address. */ function _beacon() internal view virtual returns (address) { return _getBeacon(); } /** * @dev Returns the current implementation address of the associated beacon. */ function _implementation() internal view virtual override returns (address) { return IBeacon(_getBeacon()).implementation(); } /** * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}. * * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. * * Requirements: * * - `beacon` must be a contract. * - The implementation returned by `beacon` must be a contract. */ function _setBeacon(address beacon, bytes memory data) internal virtual { _upgradeBeaconToAndCall(beacon, data, false); } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Fixer /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Hatcher can deploy, upgrade, fix and pause a set of instances called cubs. /// All cubs point to the same common implementation. interface IFixer { /// @notice Interface to implement on a Fixer contract. /// @return isFixed True if fix was properly applied function fix() external returns (bool isFixed); } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Cub /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The cub is controlled by a Hatcher in charge of providing its status details and implementation address. interface ICub { /// @notice An error occured when performing the delegatecall to the fix. /// @param fixer Address implementing the fix /// @param err The return data from the call error error FixDelegateCallError(address fixer, bytes err); /// @notice The fix method failed by returning false. /// @param fixer Added implementing the fix error FixCallError(address fixer); /// @notice A call was made while the cub was paused. /// @param caller The address that performed the call error CalledWhenPaused(address caller); error CubAlreadyInitialized(); /// @notice Emitted when several fixes have been applied. /// @param fixes List of fixes to apply event AppliedFixes(address[] fixes); /// @notice Public method that emits the AppliedFixes event. /// @dev Transparent to all callers except the cub itself /// @dev Only callable by the cub itself as a regular call /// @dev This method is used to detect the execution context (view/non-view) /// @param _fixers List of applied fixes function appliedFixes(address[] memory _fixers) external; /// @notice Applies the provided fix. /// @dev Transparent to all callers except the hatcher /// @param _fixer The address of the contract implementing the fix to apply function applyFix(address _fixer) external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Administrable Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contract provides all the utilities to handle the administration and its transfer. interface IAdministrable { /// @notice The admin address has been changed. /// @param admin The new admin address event SetAdmin(address admin); /// @notice The pending admin address has been changed. /// @param pendingAdmin The pending admin has been changed event SetPendingAdmin(address pendingAdmin); /// @notice Retrieve the admin address. /// @return adminAddress The admin address function admin() external view returns (address adminAddress); /// @notice Retrieve the pending admin address. /// @return pendingAdminAddress The pending admin address function pendingAdmin() external view returns (address pendingAdminAddress); /// @notice Propose a new admin. /// @dev Only callable by the admin /// @param _newAdmin The new admin to propose function transferAdmin(address _newAdmin) external; /// @notice Accept an admin transfer. /// @dev Only callable by the pending admin function acceptAdmin() external; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (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: * ```solidity * 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) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } } // SPDX-License-Identifier: Unlicense pragma solidity >=0.8.4; /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivFixedPointOverflow(uint256 prod1); /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivOverflow(uint256 prod1, uint256 denominator); /// @notice Emitted when one of the inputs is type(int256).min. error PRBMath__MulDivSignedInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows int256. error PRBMath__MulDivSignedOverflow(uint256 rAbs); /// @notice Emitted when the input is MIN_SD59x18. error PRBMathSD59x18__AbsInputTooSmall(); /// @notice Emitted when ceiling a number overflows SD59x18. error PRBMathSD59x18__CeilOverflow(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__DivInputTooSmall(); /// @notice Emitted when one of the intermediary unsigned results overflows SD59x18. error PRBMathSD59x18__DivOverflow(uint256 rAbs); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathSD59x18__ExpInputTooBig(int256 x); /// @notice Emitted when the input is greater than 192. error PRBMathSD59x18__Exp2InputTooBig(int256 x); /// @notice Emitted when flooring a number underflows SD59x18. error PRBMathSD59x18__FloorUnderflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format overflows SD59x18. error PRBMathSD59x18__FromIntOverflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format underflows SD59x18. error PRBMathSD59x18__FromIntUnderflow(int256 x); /// @notice Emitted when the product of the inputs is negative. error PRBMathSD59x18__GmNegativeProduct(int256 x, int256 y); /// @notice Emitted when multiplying the inputs overflows SD59x18. error PRBMathSD59x18__GmOverflow(int256 x, int256 y); /// @notice Emitted when the input is less than or equal to zero. error PRBMathSD59x18__LogInputTooSmall(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__MulInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__MulOverflow(uint256 rAbs); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__PowuOverflow(uint256 rAbs); /// @notice Emitted when the input is negative. error PRBMathSD59x18__SqrtNegativeInput(int256 x); /// @notice Emitted when the calculating the square root overflows SD59x18. error PRBMathSD59x18__SqrtOverflow(int256 x); /// @notice Emitted when addition overflows UD60x18. error PRBMathUD60x18__AddOverflow(uint256 x, uint256 y); /// @notice Emitted when ceiling a number overflows UD60x18. error PRBMathUD60x18__CeilOverflow(uint256 x); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathUD60x18__ExpInputTooBig(uint256 x); /// @notice Emitted when the input is greater than 192. error PRBMathUD60x18__Exp2InputTooBig(uint256 x); /// @notice Emitted when converting a basic integer to the fixed-point format format overflows UD60x18. error PRBMathUD60x18__FromUintOverflow(uint256 x); /// @notice Emitted when multiplying the inputs overflows UD60x18. error PRBMathUD60x18__GmOverflow(uint256 x, uint256 y); /// @notice Emitted when the input is less than 1. error PRBMathUD60x18__LogInputTooSmall(uint256 x); /// @notice Emitted when the calculating the square root overflows UD60x18. error PRBMathUD60x18__SqrtOverflow(uint256 x); /// @notice Emitted when subtraction underflows UD60x18. error PRBMathUD60x18__SubUnderflow(uint256 x, uint256 y); /// @dev Common mathematical functions used in both PRBMathSD59x18 and PRBMathUD60x18. Note that this shared library /// does not always assume the signed 59.18-decimal fixed-point or the unsigned 60.18-decimal fixed-point /// representation. When it does not, it is explicitly mentioned in the NatSpec documentation. library PRBMath { /// STRUCTS /// struct SD59x18 { int256 value; } struct UD60x18 { uint256 value; } /// STORAGE /// /// @dev How many trailing decimals can be represented. uint256 internal constant SCALE = 1e18; /// @dev Largest power of two divisor of SCALE. uint256 internal constant SCALE_LPOTD = 262144; /// @dev SCALE inverted mod 2^256. uint256 internal constant SCALE_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281; /// FUNCTIONS /// /// @notice Calculates the binary exponent of x using the binary fraction method. /// @dev Has to use 192.64-bit fixed-point numbers. /// See https://ethereum.stackexchange.com/a/96594/24693. /// @param x The exponent as an unsigned 192.64-bit fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function exp2(uint256 x) internal pure returns (uint256 result) { unchecked { // Start from 0.5 in the 192.64-bit fixed-point format. result = 0x800000000000000000000000000000000000000000000000; // Multiply the result by root(2, 2^-i) when the bit at position i is 1. None of the intermediary results overflows // because the initial result is 2^191 and all magic factors are less than 2^65. if (x & 0x8000000000000000 > 0) { result = (result * 0x16A09E667F3BCC909) >> 64; } if (x & 0x4000000000000000 > 0) { result = (result * 0x1306FE0A31B7152DF) >> 64; } if (x & 0x2000000000000000 > 0) { result = (result * 0x1172B83C7D517ADCE) >> 64; } if (x & 0x1000000000000000 > 0) { result = (result * 0x10B5586CF9890F62A) >> 64; } if (x & 0x800000000000000 > 0) { result = (result * 0x1059B0D31585743AE) >> 64; } if (x & 0x400000000000000 > 0) { result = (result * 0x102C9A3E778060EE7) >> 64; } if (x & 0x200000000000000 > 0) { result = (result * 0x10163DA9FB33356D8) >> 64; } if (x & 0x100000000000000 > 0) { result = (result * 0x100B1AFA5ABCBED61) >> 64; } if (x & 0x80000000000000 > 0) { result = (result * 0x10058C86DA1C09EA2) >> 64; } if (x & 0x40000000000000 > 0) { result = (result * 0x1002C605E2E8CEC50) >> 64; } if (x & 0x20000000000000 > 0) { result = (result * 0x100162F3904051FA1) >> 64; } if (x & 0x10000000000000 > 0) { result = (result * 0x1000B175EFFDC76BA) >> 64; } if (x & 0x8000000000000 > 0) { result = (result * 0x100058BA01FB9F96D) >> 64; } if (x & 0x4000000000000 > 0) { result = (result * 0x10002C5CC37DA9492) >> 64; } if (x & 0x2000000000000 > 0) { result = (result * 0x1000162E525EE0547) >> 64; } if (x & 0x1000000000000 > 0) { result = (result * 0x10000B17255775C04) >> 64; } if (x & 0x800000000000 > 0) { result = (result * 0x1000058B91B5BC9AE) >> 64; } if (x & 0x400000000000 > 0) { result = (result * 0x100002C5C89D5EC6D) >> 64; } if (x & 0x200000000000 > 0) { result = (result * 0x10000162E43F4F831) >> 64; } if (x & 0x100000000000 > 0) { result = (result * 0x100000B1721BCFC9A) >> 64; } if (x & 0x80000000000 > 0) { result = (result * 0x10000058B90CF1E6E) >> 64; } if (x & 0x40000000000 > 0) { result = (result * 0x1000002C5C863B73F) >> 64; } if (x & 0x20000000000 > 0) { result = (result * 0x100000162E430E5A2) >> 64; } if (x & 0x10000000000 > 0) { result = (result * 0x1000000B172183551) >> 64; } if (x & 0x8000000000 > 0) { result = (result * 0x100000058B90C0B49) >> 64; } if (x & 0x4000000000 > 0) { result = (result * 0x10000002C5C8601CC) >> 64; } if (x & 0x2000000000 > 0) { result = (result * 0x1000000162E42FFF0) >> 64; } if (x & 0x1000000000 > 0) { result = (result * 0x10000000B17217FBB) >> 64; } if (x & 0x800000000 > 0) { result = (result * 0x1000000058B90BFCE) >> 64; } if (x & 0x400000000 > 0) { result = (result * 0x100000002C5C85FE3) >> 64; } if (x & 0x200000000 > 0) { result = (result * 0x10000000162E42FF1) >> 64; } if (x & 0x100000000 > 0) { result = (result * 0x100000000B17217F8) >> 64; } if (x & 0x80000000 > 0) { result = (result * 0x10000000058B90BFC) >> 64; } if (x & 0x40000000 > 0) { result = (result * 0x1000000002C5C85FE) >> 64; } if (x & 0x20000000 > 0) { result = (result * 0x100000000162E42FF) >> 64; } if (x & 0x10000000 > 0) { result = (result * 0x1000000000B17217F) >> 64; } if (x & 0x8000000 > 0) { result = (result * 0x100000000058B90C0) >> 64; } if (x & 0x4000000 > 0) { result = (result * 0x10000000002C5C860) >> 64; } if (x & 0x2000000 > 0) { result = (result * 0x1000000000162E430) >> 64; } if (x & 0x1000000 > 0) { result = (result * 0x10000000000B17218) >> 64; } if (x & 0x800000 > 0) { result = (result * 0x1000000000058B90C) >> 64; } if (x & 0x400000 > 0) { result = (result * 0x100000000002C5C86) >> 64; } if (x & 0x200000 > 0) { result = (result * 0x10000000000162E43) >> 64; } if (x & 0x100000 > 0) { result = (result * 0x100000000000B1721) >> 64; } if (x & 0x80000 > 0) { result = (result * 0x10000000000058B91) >> 64; } if (x & 0x40000 > 0) { result = (result * 0x1000000000002C5C8) >> 64; } if (x & 0x20000 > 0) { result = (result * 0x100000000000162E4) >> 64; } if (x & 0x10000 > 0) { result = (result * 0x1000000000000B172) >> 64; } if (x & 0x8000 > 0) { result = (result * 0x100000000000058B9) >> 64; } if (x & 0x4000 > 0) { result = (result * 0x10000000000002C5D) >> 64; } if (x & 0x2000 > 0) { result = (result * 0x1000000000000162E) >> 64; } if (x & 0x1000 > 0) { result = (result * 0x10000000000000B17) >> 64; } if (x & 0x800 > 0) { result = (result * 0x1000000000000058C) >> 64; } if (x & 0x400 > 0) { result = (result * 0x100000000000002C6) >> 64; } if (x & 0x200 > 0) { result = (result * 0x10000000000000163) >> 64; } if (x & 0x100 > 0) { result = (result * 0x100000000000000B1) >> 64; } if (x & 0x80 > 0) { result = (result * 0x10000000000000059) >> 64; } if (x & 0x40 > 0) { result = (result * 0x1000000000000002C) >> 64; } if (x & 0x20 > 0) { result = (result * 0x10000000000000016) >> 64; } if (x & 0x10 > 0) { result = (result * 0x1000000000000000B) >> 64; } if (x & 0x8 > 0) { result = (result * 0x10000000000000006) >> 64; } if (x & 0x4 > 0) { result = (result * 0x10000000000000003) >> 64; } if (x & 0x2 > 0) { result = (result * 0x10000000000000001) >> 64; } if (x & 0x1 > 0) { result = (result * 0x10000000000000001) >> 64; } // We're doing two things at the same time: // // 1. Multiply the result by 2^n + 1, where "2^n" is the integer part and the one is added to account for // the fact that we initially set the result to 0.5. This is accomplished by subtracting from 191 // rather than 192. // 2. Convert the result to the unsigned 60.18-decimal fixed-point format. // // This works because 2^(191-ip) = 2^ip / 2^191, where "ip" is the integer part "2^n". result *= SCALE; result >>= (191 - (x >> 64)); } } /// @notice Finds the zero-based index of the first one in the binary representation of x. /// @dev See the note on msb in the "Find First Set" Wikipedia article https://en.wikipedia.org/wiki/Find_first_set /// @param x The uint256 number for which to find the index of the most significant bit. /// @return msb The index of the most significant bit as an uint256. function mostSignificantBit(uint256 x) internal pure returns (uint256 msb) { if (x >= 2**128) { x >>= 128; msb += 128; } if (x >= 2**64) { x >>= 64; msb += 64; } if (x >= 2**32) { x >>= 32; msb += 32; } if (x >= 2**16) { x >>= 16; msb += 16; } if (x >= 2**8) { x >>= 8; msb += 8; } if (x >= 2**4) { x >>= 4; msb += 4; } if (x >= 2**2) { x >>= 2; msb += 2; } if (x >= 2**1) { // No need to shift x any more. msb += 1; } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv. /// /// Requirements: /// - The denominator cannot be zero. /// - The result must fit within uint256. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The multiplicand as an uint256. /// @param y The multiplier as an uint256. /// @param denominator The divisor as an uint256. /// @return result The result as an uint256. function mulDiv( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 result) { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { unchecked { result = prod0 / denominator; } return result; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (prod1 >= denominator) { revert PRBMath__MulDivOverflow(prod1, denominator); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. unchecked { // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 lpotdod = denominator & (~denominator + 1); assembly { // Divide denominator by lpotdod. denominator := div(denominator, lpotdod) // Divide [prod1 prod0] by lpotdod. prod0 := div(prod0, lpotdod) // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one. lpotdod := add(div(sub(0, lpotdod), lpotdod), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * lpotdod; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /// @notice Calculates floor(x*y÷1e18) with full precision. /// /// @dev Variant of "mulDiv" with constant folding, i.e. in which the denominator is always 1e18. Before returning the /// final result, we add 1 if (x * y) % SCALE >= HALF_SCALE. Without this, 6.6e-19 would be truncated to 0 instead of /// being rounded to 1e-18. See "Listing 6" and text above it at https://accu.org/index.php/journals/1717. /// /// Requirements: /// - The result must fit within uint256. /// /// Caveats: /// - The body is purposely left uncommented; see the NatSpec comments in "PRBMath.mulDiv" to understand how this works. /// - It is assumed that the result can never be type(uint256).max when x and y solve the following two equations: /// 1. x * y = type(uint256).max * SCALE /// 2. (x * y) % SCALE >= SCALE / 2 /// /// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number. /// @param y The multiplier as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function mulDivFixedPoint(uint256 x, uint256 y) internal pure returns (uint256 result) { uint256 prod0; uint256 prod1; assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } if (prod1 >= SCALE) { revert PRBMath__MulDivFixedPointOverflow(prod1); } uint256 remainder; uint256 roundUpUnit; assembly { remainder := mulmod(x, y, SCALE) roundUpUnit := gt(remainder, 499999999999999999) } if (prod1 == 0) { unchecked { result = (prod0 / SCALE) + roundUpUnit; return result; } } assembly { result := add( mul( or( div(sub(prod0, remainder), SCALE_LPOTD), mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, SCALE_LPOTD), SCALE_LPOTD), 1)) ), SCALE_INVERSE ), roundUpUnit ) } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev An extension of "mulDiv" for signed numbers. Works by computing the signs and the absolute values separately. /// /// Requirements: /// - None of the inputs can be type(int256).min. /// - The result must fit within int256. /// /// @param x The multiplicand as an int256. /// @param y The multiplier as an int256. /// @param denominator The divisor as an int256. /// @return result The result as an int256. function mulDivSigned( int256 x, int256 y, int256 denominator ) internal pure returns (int256 result) { if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) { revert PRBMath__MulDivSignedInputTooSmall(); } // Get hold of the absolute values of x, y and the denominator. uint256 ax; uint256 ay; uint256 ad; unchecked { ax = x < 0 ? uint256(-x) : uint256(x); ay = y < 0 ? uint256(-y) : uint256(y); ad = denominator < 0 ? uint256(-denominator) : uint256(denominator); } // Compute the absolute value of (x*y)÷denominator. The result must fit within int256. uint256 rAbs = mulDiv(ax, ay, ad); if (rAbs > uint256(type(int256).max)) { revert PRBMath__MulDivSignedOverflow(rAbs); } // Get the signs of x, y and the denominator. uint256 sx; uint256 sy; uint256 sd; assembly { sx := sgt(x, sub(0, 1)) sy := sgt(y, sub(0, 1)) sd := sgt(denominator, sub(0, 1)) } // XOR over sx, sy and sd. This is checking whether there are one or three negative signs in the inputs. // If yes, the result should be negative. result = sx ^ sy ^ sd == 0 ? -int256(rAbs) : int256(rAbs); } /// @notice Calculates the square root of x, rounding down. /// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The uint256 number for which to calculate the square root. /// @return result The result as an uint256. function sqrt(uint256 x) internal pure returns (uint256 result) { if (x == 0) { return 0; } // Set the initial guess to the least power of two that is greater than or equal to sqrt(x). uint256 xAux = uint256(x); result = 1; if (xAux >= 0x100000000000000000000000000000000) { xAux >>= 128; result <<= 64; } if (xAux >= 0x10000000000000000) { xAux >>= 64; result <<= 32; } if (xAux >= 0x100000000) { xAux >>= 32; result <<= 16; } if (xAux >= 0x10000) { xAux >>= 16; result <<= 8; } if (xAux >= 0x100) { xAux >>= 8; result <<= 4; } if (xAux >= 0x10) { xAux >>= 4; result <<= 2; } if (xAux >= 0x8) { result <<= 1; } // The operations can never overflow because the result is max 2^127 when it enters this block. unchecked { result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; // Seven iterations should be enough uint256 roundedDownResult = x / result; return result >= roundedDownResult ? roundedDownResult : result; } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @dev Library holding bytes32 custom types // slither-disable-next-line naming-convention library types { type Uint256 is bytes32; type Address is bytes32; type Bytes32 is bytes32; type Bool is bytes32; type String is bytes32; type Mapping is bytes32; type Array is bytes32; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.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 overridden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _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 overridden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} } // 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._ */ 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) (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 (last updated v4.8.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 * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.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 functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
File 5 of 5: vExitQueue
// SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/Fixable.sol"; import "utils.sol/NFT.sol"; import "utils.sol/Implementation.sol"; import "utils.sol/types/bool.sol"; import "openzeppelin-contracts/utils/Base64.sol"; import "./lib/LibStringify.sol"; import "./ctypes/ticket_array.sol"; import "./ctypes/cask_array.sol"; import "./interfaces/IvExitQueue.sol"; import "./interfaces/IvPool.sol"; import "./interfaces/IvFactory.sol"; /// @title Exit Queue /// @author mortimr @ Kiln /// @notice The exit queue stores exit requests until they are filled and claimable /// /// ⢀⣀ ⢀⣤⣤⣤⠄⣠⣤⣤⠄⢀⣀⡀ /// ⢀⣾⣿⠏⢰⣿⣿⣿⠃⣰⣿⣿⠁⣴⣿⣿⣿⣷⡀ /// ⣾⣿⡟⢀⣿⣿⣿⡏⢠⣿⣿⡇⢰⣿⣿⣿⣿⣿⣷ /// ⢸⣿⣿⡇⢸⣿⣿⣿⡇⢸⣿⣿ ⢸⣿⣿⣿⣿⣿⣿ /// ⠈⣿⣿⡇⠸⣿⣿⣿⡇⢸⣿⣿⡆⢸⣿⣿⠟⢿⣿⣿ /// ⠘⠛⠛ ⠛⠛⠛⠃⠈⠻⠿⠧⠈⣿⡇ ⢸⣿⠃ /// ⢀⣤⠄⢀⣶⣿⣿⡟⢠⣾⣿⠇⢀⣤⣤⣄⠛⠛⠛⢁ ⣤⣤⣄ ⢀⣤⠄⢀⣶⣿⣿⡟⢠⣾⣿⠇⢀⣤⣤⣄ ⣤⣤⣄ /// ⣴⣿⡏⢠⣿⣿⣿⡟⢠⣿⣿⠃⣰⣿⣿⣿⣿⣧ ⢠⣿⣿⣿⣿⣧ ⣴⣿⡏⢠⣿⣿⣿⡟⢠⣿⣿⠃⣰⣿⣿⣿⣿⣧ ⢠⣿⣿⣿⣿⣧ /// ⢰⣿⣿ ⣾⣿⣿⣿⠁⣼⣿⡏⢠⣿⣿⣿⣿⣿⣿⡄ ⣿⣿⣿⣿⣿⣿⡇ ⢰⣿⣿ ⣾⣿⣿⣿⠁⣼⣿⡏⢠⣿⣿⣿⣿⣿⣿⡄ ⣿⣿⣿⣿⣿⣿⡇ /// ⢸⣿⣿ ⣿⣿⣿⣿ ⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿⡇ ⣿⣿⣿⣿⣿⣿⡇ ⢸⣿⣿ ⣿⣿⣿⣿ ⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿⡇ ⣿⣿⣿⣿⣿⣿⡇ /// ⠘⣿⣿ ⣿⣿⣿⣿⡀⢻⣿⣧⠈⣿⣿⠟⠙⣿⣿⠁ ⢿⣿⡟⠙⢿⣿⠇ ⠘⣿⣿ ⣿⣿⣿⣿⡀⢻⣿⣧⠈⣿⣿⠟⠙⣿⣿⠁ ⢿⣿⡟⠙⢿⣿⠇ /// ⠙⢿⣇⠘⢿⣿⣿⣧⠈⣿⣿⣆⠘⣿⣄⣠⣾⠃⣰⣧⠘⢿⣇⢀⣾⠏ ⠙⢿⣇⠘⢿⣿⣿⣧⠈⣿⣿⣆⠘⣿⣄⣠⣾⠃⣰⣧⠘⢿⣇⢀⣾⠏ /// ⠈⠁⠈⠛⠛⠛⠃⠈⠛⠛⠓ ⠉⠉ ⠐⠛⠛⠓ ⠉⠉⠁ ⠈⠁⠈⠛⠛⠛⠃⠈⠛⠛⠓ ⠉⠉ ⠐⠛⠛⠓ ⠉⠉⠁ /// // slither-disable-next-line naming-convention contract vExitQueue is NFT, Fixable, Initializable, Implementation, IvExitQueue { using LUint256 for types.Uint256; using LAddress for types.Address; using LBool for types.Bool; using LString for types.String; using LTicketArray for ctypes.TicketArray; using LCaskArray for ctypes.CaskArray; /// @dev Address of the associated vPool /// @dev Slot: keccak256(bytes("exitQueue.1.pool"))) - 1 types.Address internal constant $pool = types.Address.wrap(0xdcdd87edea8fcbdc6d50bb4863c8269eed833245e48ec3e4f64dc4cd88a27283); /// @dev Total amount of unclaimed funds in the exit queue - 1 /// @dev Slot: keccak256(bytes("exitQueue.1.unclaimedFunds"))) types.Uint256 internal constant $unclaimedFunds = types.Uint256.wrap(0x51fae72b3be6f7b8c2f4de519c1a9fb3f8624c4c7d1f85109b6659ae4958c29a); /// @dev Flag enabling/disabling transfers /// @dev Slot: keccak256(bytes("exitQueue.1.transferEnabled"))) - 1 types.Bool internal constant $transferEnabled = types.Bool.wrap(0xc1bfc3030aebadb3bfaa3fbc59cf364f7dee6ab92429159a4bfdf02fa88336a0); /// @dev Token URI image URL /// @dev Slot: keccak256(bytes("exitQueue.1.tokenUriImageUrl"))) - 1 types.String internal constant $tokenUriImageUrl = types.String.wrap(0x0f0463b3f5083af4c7135d28606a2c0eaa2bd9e3f9f62db1539e47244df8dc49); /// @dev Array of tickets /// @dev Slot: keccak256(bytes("exitQueue.1.tickets"))) - 1 ctypes.TicketArray internal constant $tickets = ctypes.TicketArray.wrap(0x409fdfd8838fda00128ca5d502af2ba15c034ca4130776e2ed6d3eb7811e3481); /// @dev Array of casks /// @dev Slot: keccak256(bytes("exitQueue.1.casks"))) - 1 ctypes.CaskArray internal constant $casks = ctypes.CaskArray.wrap(0x39a5c864ceb6f99a196a385a148476994e3952fd6d71d040a2339a143eaeabe1); /// @dev Resolution error code for a ticket that is out of bounds int64 internal constant TICKET_ID_OUT_OF_BOUNDS = -1; /// @dev Resolution error code for a ticket that has already been claimed int64 internal constant TICKET_ALREADY_CLAIMED = -2; /// @dev Resolution error code for a ticket that is pending fulfillment int64 internal constant TICKET_PENDING = -3; /// @notice Prevents calls not coming from the associated vPool modifier onlyPool() { if (msg.sender != $pool.get()) { revert LibErrors.Unauthorized(msg.sender, $pool.get()); } _; } /// @notice Prevents calls not coming from the vFactory admin modifier onlyAdmin() { { address admin = IvFactory(_castedPool().factory()).admin(); if (msg.sender != admin) { revert LibErrors.Unauthorized(msg.sender, admin); } } _; } /// @inheritdoc IvExitQueue // slither-disable-next-line missing-zero-check function initialize(address vpool, string calldata newTokenUriImageUrl) external init(0) { _setTokenUriImageUrl(newTokenUriImageUrl); LibSanitize.notZeroAddress(vpool); $pool.set(vpool); emit SetPool(vpool); } /// @inheritdoc IvExitQueue function tokenUriImageUrl() external view returns (string memory) { return $tokenUriImageUrl.get(); } /// @notice Get the Exit Queue name from the associated vPool -> Factory. /// @dev The name is mutable (can be updated by the Factory admin). /// @return The name of the Exit Queue. function name() external view override returns (string memory) { // slither-disable-next-line unused-return (string memory operatorName,,) = IvFactory(_castedPool().factory()).metadata(); return string(abi.encodePacked(operatorName, " Exit Queue")); } /// @inheritdoc NFT function symbol() external pure override returns (string memory) { return "vEQ"; } /// @inheritdoc IERC721Metadata function tokenURI(uint256 tokenId) external view override(NFT) returns (string memory) { _requireExists(tokenId); uint256 tokenIdx = _getTicketIdx(tokenId); ctypes.Ticket memory t = $tickets.get()[tokenIdx]; ctypes.Cask[] storage caskArray = $casks.get(); uint256 claimable = 0; uint256 queueSize = 0; { ctypes.Cask memory c = caskArray.length > 0 ? caskArray[caskArray.length - 1] : ctypes.Cask({position: 0, size: 0, value: 0}); // | CASK | // | TICKET | if (c.position > t.position + t.size) { claimable = t.size; // | CASK | // | TICKET | } else if (c.position >= t.position) { claimable = t.size - (c.position + c.size >= t.position + t.size ? 0 : (t.position + t.size) - (c.position + c.size)); // | CASK | // | TICKET | } else if (c.position < t.position && t.position < c.position + c.size) { claimable = (c.position + c.size) - t.position; } queueSize = c.position + c.size; } bytes memory fullImageUrl = abi.encodePacked($tokenUriImageUrl.get(), "/", Strings.toHexString(address(this)), "/", Strings.toString(tokenId)); return string( abi.encodePacked( "data:application/json;base64,", Base64.encode( abi.encodePacked( "{", "\\"name\\":\\"Exit Ticket #", Strings.toString(tokenIdx), "\\",", "\\"description\\":\\"This exit ticket can be used to claim funds from the exit queue contract once it is fulfilled.\\",", _generateAttributes(t, claimable, queueSize), "\\"image_url\\":\\"", fullImageUrl, "\\"}" ) ) ) ); } /// @inheritdoc IvExitQueue function transferEnabled() external view returns (bool) { return $transferEnabled.get(); } /// @inheritdoc IvExitQueue function unclaimedFunds() external view returns (uint256) { return $unclaimedFunds.get(); } /// @inheritdoc IvExitQueue function ticketIdAtIndex(uint32 idx) external view returns (uint256) { return _getTicketId(idx, $tickets.get()[idx]); } /// @inheritdoc IvExitQueue function ticket(uint256 id) external view returns (ctypes.Ticket memory) { uint256 idx = _getTicketIdx(id); ctypes.Ticket[] storage ticketArray = $tickets.get(); if (idx >= ticketArray.length) { revert InvalidTicketId(id); } return ticketArray[idx]; } /// @inheritdoc IvExitQueue function ticketCount() external view returns (uint256) { return $tickets.get().length; } /// @inheritdoc IvExitQueue function cask(uint32 id) external view returns (ctypes.Cask memory) { ctypes.Cask[] storage caskArray = $casks.get(); if (id >= caskArray.length) { revert InvalidCaskId(id); } return caskArray[id]; } /// @inheritdoc IvExitQueue function caskCount() external view returns (uint256) { return $casks.get().length; } /// @inheritdoc IvExitQueue function resolve(uint256[] memory ticketIds) external view returns (int64[] memory caskIdsOrErrors) { uint256 ticketIdsLength = ticketIds.length; caskIdsOrErrors = new int64[](ticketIdsLength); uint256 totalTicketCount = $tickets.get().length; uint256 totalCaskCount = $casks.get().length; for (uint256 idx = 0; idx < ticketIdsLength;) { caskIdsOrErrors[idx] = _resolve(ticketIds[idx], totalTicketCount, totalCaskCount); unchecked { ++idx; } } } /// @inheritdoc IvExitQueue function feed(uint256 shares) external payable onlyPool { LibSanitize.notNullValue(shares); LibSanitize.notNullValue(msg.value); ctypes.Cask[] storage caskArray = $casks.get(); uint256 casksLength = caskArray.length; ctypes.Cask memory lastCask = casksLength > 0 ? caskArray[casksLength - 1] : ctypes.Cask({position: 0, size: 0, value: 0}); ctypes.Cask memory newCask = ctypes.Cask({position: lastCask.position + lastCask.size, size: uint128(shares), value: uint128(msg.value)}); caskArray.push(newCask); emit ReceivedCask(uint32(casksLength), newCask); } /// @inheritdoc IvExitQueue function pull(uint256 max) external onlyPool { uint256 currentUnclaimedFunds = $unclaimedFunds.get(); uint256 maxPullable = LibUint256.min(max, currentUnclaimedFunds); if (maxPullable > 0) { _setUnclaimedFunds(currentUnclaimedFunds - maxPullable); emit SuppliedEther(maxPullable); _castedPool().injectEther{value: maxPullable}(); } } /// @inheritdoc IvPoolSharesReceiver //slither-disable-next-line assembly function onvPoolSharesReceived(address, address from, uint256 amount, bytes memory data) external override onlyPool returns (bytes4) { LibSanitize.notNullValue(amount); if (data.length == 20) { // If the data appears to be a packed encoded address we print a ticket to that address instead of the sender address to; assembly { // After skipping the length element of data, the first element (20 bytes padded on the right to 32 bytes) is // converted to an actual address by right shifting. to := shr(96, mload(add(data, 32))) } _printTicket(uint128(amount), to); } else { _printTicket(uint128(amount), from); } return IvPoolSharesReceiver.onvPoolSharesReceived.selector; } /// @inheritdoc IvExitQueue function setTokenUriImageUrl(string calldata newTokenUriImageUrl) external onlyAdmin { _setTokenUriImageUrl(newTokenUriImageUrl); } /// @inheritdoc IvExitQueue function setTransferEnabled(bool value) external onlyAdmin { _setTransferEnabled(value); } struct ClaimInternalVariables { uint256 ticketIdsLength; uint256 totalTicketCount; address[] recipients; uint256[] payments; uint256 usedRecipients; ConsumeTicketParameters params; } /// @inheritdoc IvExitQueue // slither-disable-next-line arbitrary-send-eth,calls-loop,reentrancy-events,cyclomatic-complexity function claim(uint256[] calldata ticketIds, uint32[] calldata caskIds, uint16 maxClaimDepth) external returns (ClaimStatus[] memory statuses) { // slither-disable-next-line uninitialized-local ClaimInternalVariables memory __; __.ticketIdsLength = ticketIds.length; if (__.ticketIdsLength == 0 || __.ticketIdsLength != caskIds.length) { revert InvalidLengths(); } __.totalTicketCount = $tickets.get().length; __.params.totalCaskCount = $casks.get().length; statuses = new ClaimStatus[](ticketIds.length); __.recipients = new address[](ticketIds.length); __.payments = new uint256[](ticketIds.length); __.usedRecipients = 0; // slither-disable-next-line uninitialized-local for (uint256 idx; idx < __.ticketIdsLength;) { __.params.ticketId = ticketIds[idx]; __.params.ticketIdx = _getTicketIdx(__.params.ticketId); __.params.caskId = caskIds[idx]; __.params.depth = maxClaimDepth; __.params.ethToPay = 0; // this line reverts if the ticket id doesn't exist address owner = ownerOf(ticketIds[idx]); __.params.t = $tickets.get()[__.params.ticketIdx]; if (__.params.t.size == 0) { statuses[idx] = ClaimStatus.SKIPPED; unchecked { ++idx; } continue; } if (__.params.caskId >= __.params.totalCaskCount) { revert InvalidCaskId(__.params.caskId); } __.params.c = $casks.get()[__.params.caskId]; if (!_matching(__.params.t, __.params.c)) { revert TicketNotMatchingCask(__.params.ticketId, __.params.caskId); } _consumeTicket(__.params); if (__.params.t.size > 0) { uint256 ticketIdx = _getTicketIdx(ticketIds[idx]); _burn(ticketIds[idx]); uint256 newTicketId = _getTicketId(ticketIdx, $tickets.get()[ticketIdx]); _mint(owner, newTicketId); emit TicketIdUpdated(ticketIds[idx], newTicketId, uint32(ticketIdx)); } statuses[idx] = __.params.t.size > 0 ? ClaimStatus.PARTIALLY_CLAIMED : ClaimStatus.CLAIMED; if (__.params.ethToPay > 0) { int256 ownerIndex = -1; for (uint256 recipientIdx = 0; recipientIdx < __.usedRecipients;) { if (__.recipients[recipientIdx] == owner) { ownerIndex = int256(recipientIdx); break; } unchecked { ++recipientIdx; } } if (ownerIndex == -1) { __.recipients[__.usedRecipients] = owner; __.payments[__.usedRecipients] = __.params.ethToPay; unchecked { ++__.usedRecipients; } } else { __.payments[uint256(ownerIndex)] += __.params.ethToPay; } } unchecked { ++idx; } } for (uint256 recipientIdx = 0; recipientIdx < __.usedRecipients;) { address recipient = __.recipients[recipientIdx]; uint256 payment = __.payments[recipientIdx]; // slither-disable-next-line missing-zero-check,low-level-calls (bool success, bytes memory reason) = recipient.call{value: payment}(""); if (!success) { revert ClaimTransferFailed(recipient, reason); } emit Payment(recipient, payment); unchecked { ++recipientIdx; } } } /// @dev Internal utility function to retrieve the string status of a ticket /// @param t The ticket to get the status of /// @param claimable The amount of the ticket that is claimable /// @return The status of the ticket function _getStatusString(ctypes.Ticket memory t, uint256 claimable) internal pure returns (string memory) { if (claimable == 0) { return "Not yet claimable"; } else if (claimable < t.size) { return "Partially claimable"; } return "Fully claimable"; } /// @dev Internal utility function to generate the attributes of a ticket /// @param t The ticket to get the attributes of /// @param claimable The amount of the ticket that is claimable /// @return The attributes of the ticket function _generateAttributes(ctypes.Ticket memory t, uint256 claimable, uint256 queueSize) internal pure returns (bytes memory) { return abi.encodePacked( "\\"attributes\\":[{\\"trait_type\\":\\"Queue position\\",\\"value\\":", LibStringify.uintToDecimalString(t.position, 18, 3), ",\\"display_type\\":\\"number\\",\\"max_value\\":", LibStringify.uintToDecimalString(queueSize, 18, 3), "},{\\"trait_type\\":\\"Claimable amount\\",\\"value\\":", LibStringify.uintToDecimalString(claimable, 18, 3), ",\\"display_type\\":\\"number\\",\\"max_value\\":", LibStringify.uintToDecimalString(t.size, 18, 3), "},{\\"trait_type\\":\\"Status\\",\\"value\\":\\"", _getStatusString(t, claimable), "\\"}]," ); } /// @dev Internal hook happening at each transfer. /// To override. function _onTransfer(address, address, uint256) internal view override { if (!$transferEnabled.get()) { revert TransferDisabled(); } } /// @dev Internal hook happening at each mint. /// To override. /// @param to The address receiving the token /// @param tokenId The token id function _onMint(address to, uint256 tokenId) internal override {} /// @dev Internal hook happening at each burn. /// To override. /// @param tokenId The token id function _onBurn(uint256 tokenId) internal override {} /// @dev Internal utility to retrieve the vPool address casted to the vPool interface /// @return The vPool address casted to the vPool interface function _castedPool() internal view returns (IvPool) { return IvPool($pool.get()); } /// @dev Internal utility to check if a ticket is claimable on a cask /// @param t The ticket to check /// @param p The cask to check /// @return True if the ticket is claimable on the cask function _matching(ctypes.Ticket memory t, ctypes.Cask memory p) internal pure returns (bool) { return (t.position < p.position + p.size && t.position >= p.position); } /// @dev Internal utility to perform a dichotomy search to find the cask matching a ticket /// @param ticketIdx The index of the ticket to find the cask for /// @return caskId The cask id matching the ticket function _searchCaskForTicket(uint256 ticketIdx) internal view returns (uint32 caskId) { ctypes.Cask[] storage caskArray = $casks.get(); uint32 right = uint32(caskArray.length - 1); ctypes.Ticket memory t = $tickets.get()[ticketIdx]; if (_matching(t, caskArray[right])) { return right; } uint32 left = 0; if (_matching(t, caskArray[left])) { return left; } while (left != right) { uint32 middle = (left + right) >> 1; ctypes.Cask memory middleC = caskArray[middle]; if (_matching(t, middleC)) { return middle; } if (t.position < middleC.position) { right = middle; } else { left = middle; } } return left; } /// @dev Internal utility to resolve a ticket /// @param ticketId The ticket to resolve /// @param totalTicketCount The total number of tickets /// @param totalCaskCount The total number of casks /// @return caskIdOrError The cask id matching the ticket or an error code function _resolve(uint256 ticketId, uint256 totalTicketCount, uint256 totalCaskCount) internal view returns (int64 caskIdOrError) { uint256 ticketIdx = _getTicketIdx(ticketId); if (ticketIdx >= totalTicketCount) { return TICKET_ID_OUT_OF_BOUNDS; } ctypes.Ticket memory t = $tickets.get()[ticketIdx]; if (t.size == 0) { return TICKET_ALREADY_CLAIMED; } if (totalCaskCount == 0 || $casks.get()[totalCaskCount - 1].position + $casks.get()[totalCaskCount - 1].size <= t.position) { return TICKET_PENDING; } return int64(uint64(_searchCaskForTicket(ticketIdx))); } /// @dev Retrieves the ticket id from its index and size. /// The ticket id is dynamic, every time someone performs a partial claim of the ticket, its id changes. /// If the claim is complete, the ticket is burned. This would lower secondary market attack vectors that /// include claiming before selling. /// @param ticketIndex The index of the ticket /// @param t The ticket /// @return The ticket id function _getTicketId(uint256 ticketIndex, ctypes.Ticket memory t) internal pure returns (uint256) { return ticketIndex << 128 | uint256(t.size); } /// @dev Retrieves the ticket index from its id /// @param ticketId The ticket id /// @return The ticket index function _getTicketIdx(uint256 ticketId) internal pure returns (uint256) { return ticketId >> 128; } /// @dev Internal utility to create a new ticket /// @param amount The amount of shares in the ticket /// @param owner The owner of the ticket function _printTicket(uint128 amount, address owner) internal { IvPool pool = _castedPool(); uint256 totalUnderlyingSupply = pool.totalUnderlyingSupply(); uint256 totalSupply = pool.totalSupply(); ctypes.Ticket[] storage ticketArray = $tickets.get(); uint256 ticketsLength = ticketArray.length; ctypes.Ticket memory lastTicket = ticketArray.length > 0 ? ticketArray[ticketsLength - 1] : ctypes.Ticket({position: 0, size: 0, maxExitable: 0}); ctypes.Ticket memory newTicket = ctypes.Ticket({ position: lastTicket.position + lastTicket.size, size: amount, maxExitable: uint128(LibUint256.mulDiv(amount, totalUnderlyingSupply, totalSupply)) }); uint256 ticketId = _getTicketId(ticketsLength, newTicket); ticketArray.push(newTicket); _mint(owner, ticketId); emit PrintedTicket(owner, uint32(ticketsLength), ticketId, newTicket); } /// @notice The parameters of the consume call /// @param ticketId The ticket id /// @param ticketIdx The index of the ticket /// @param t The ticket itself /// @param caskId The cask id /// @param c The cask itself /// @param totalCaskCount The total number of casks /// @param depth The initial depth of the consume call /// @param ethToPay The resulting eth to pay the user struct ConsumeTicketParameters { uint256 ticketId; uint256 ticketIdx; ctypes.Ticket t; uint32 caskId; ctypes.Cask c; uint256 totalCaskCount; uint16 depth; uint256 ethToPay; } /// @dev Internal utility to consume a ticket. /// Will call itself recursively to consume the ticket on all the casks it overlaps. /// Recursive calls are limited to the initial depth. /// @param params The parameters of the consume call function _consumeTicket(ConsumeTicketParameters memory params) internal { // we compute the end position of the cask uint256 caskEnd = params.c.position + params.c.size; // we compute the amount of shares and eth that overlap between the ticket and the cask uint128 overlappingAmount = uint128(LibUint256.min(params.t.size, caskEnd - params.t.position)); uint128 overlappingEthAmount = uint128(LibUint256.mulDiv(overlappingAmount, params.c.value, params.c.size)); uint128 maxRedeemableEthAmount = uint128(LibUint256.mulDiv(overlappingAmount, params.t.maxExitable, params.t.size)); // we initialize the unclaimable amount to 0 before checking if the ticket is exceeding the capped ticket rate uint256 unclaimableAmount = 0; // then we check if the overlapping amount is not exceeding the capped ticket rate // and if it's the case we adjust the amount of eth we can pay if (maxRedeemableEthAmount < overlappingEthAmount) { unclaimableAmount = overlappingEthAmount - maxRedeemableEthAmount; overlappingEthAmount = maxRedeemableEthAmount; _setUnclaimedFunds($unclaimedFunds.get() + unclaimableAmount); } // we update the ticket in memory params.t.position += overlappingAmount; params.t.size -= overlappingAmount; params.t.maxExitable -= overlappingEthAmount; // we update the total to pay for this ticket params.ethToPay += overlappingEthAmount; // we log the step emit FilledTicket(params.ticketId, params.caskId, uint128(overlappingAmount), overlappingEthAmount, unclaimableAmount); // if // - the ticket is not empty // - there are more casks to consume // - we are not at the maximum depth // then we call this method recursively with the next cask // otherwise we update the ticket in storage and burn it if it's empty if (params.t.size > 0 && params.caskId + 1 < params.totalCaskCount && params.depth > 0) { params.caskId += 1; params.c = $casks.get()[params.caskId]; params.depth -= 1; _consumeTicket(params); } else { if (params.t.size == 0) { _burn(params.ticketId); } ctypes.Ticket[] storage ticketArray = $tickets.get(); ticketArray[params.ticketIdx] = params.t; } } /// @dev Internal utility to set the unclaimed funds buffer /// @param newUnclaimedFunds The new unclaimed funds buffer function _setUnclaimedFunds(uint256 newUnclaimedFunds) internal { $unclaimedFunds.set(newUnclaimedFunds); emit SetUnclaimedFunds(newUnclaimedFunds); } /// @dev Internal utility to set the transfer enabled flag /// @param value The new transfer enabled flag function _setTransferEnabled(bool value) internal { $transferEnabled.set(value); emit SetTransferEnabled(value); } /// @dev Internal utility to set the token URI image URL /// @param newTokenUriImageUrl The new token URI image URL function _setTokenUriImageUrl(string calldata newTokenUriImageUrl) internal { LibSanitize.notEmptyString(newTokenUriImageUrl); $tokenUriImageUrl.set(newTokenUriImageUrl); emit SetTokenUriImageUrl($tokenUriImageUrl.get()); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./interfaces/IFixable.sol"; /// @title Fixable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Fixable contract can be used on cubs to expose a safe noop to force a fix. abstract contract Fixable is IFixable { /// @inheritdoc IFixable function fix() external {} } // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; import "openzeppelin-contracts/token/ERC721/IERC721Receiver.sol"; import "./types/address.sol"; import "./types/string.sol"; import "./types/mapping.sol"; import "./libs/LibSanitize.sol"; import "./Initializable.sol"; import "./interfaces/INFT.sol"; import "./uctypes/operator_approvals.sol"; /// @title NFT /// @author mortimr @ Kiln /// @notice NFT contract using utils.sol storage format. // slither-disable-next-line unimplemented-functions abstract contract NFT is INFT { using LString for types.String; using LUint256 for types.Uint256; using LMapping for types.Mapping; using LOperatorApprovalsMapping for uctypes.OperatorApprovalsMapping; using CUint256 for uint256; using CAddress for address; /// @dev ERC721 name of the contract. /// @dev Slot: keccak256(bytes("nft.1.name")) - 1 types.String internal constant $name = types.String.wrap(0x8be0d77374e3002afd46fd09ae2c8e3afc7315322504f7f1a09d189f4925e72f); /// @dev ERC721 symbol of the contract. /// @dev Slot: keccak256(bytes("nft.1.symbol")) - 1 types.String internal constant $symbol = types.String.wrap(0xddad2df2277e0186b34991db0b7ceafa36b49b76d0a1e87f6e4d44b6b17a207f); /// @dev Internal ID counter to keep track of minted tokens. /// @dev Slot: keccak256(bytes("nft.1.mintCounter")) - 1 types.Uint256 internal constant $mintCounter = types.Uint256.wrap(0x3d706fc25ad0e96a2c3fb1b58cdd70ba377f331d59f761caecaf2f3a236d99a1); /// @dev Internal burn counter used to keep track of the total supply. /// @dev Slot: keccak256(bytes("nft.1.burnCounter")) - 1 types.Uint256 internal constant $burnCounter = types.Uint256.wrap(0x0644144c18bf2aa8e15d5433cc3f6e2273ab9ccd122cd4f430275a2997cc0dc2); /// @dev Internal mapping that holds the links between owners and NFT IDs. /// @dev Type: mapping (uint256 => address) /// @dev Slot: keccak256(bytes("nft.1.owners")) - 1 types.Mapping internal constant $owners = types.Mapping.wrap(0xc1f66d46ebf7070ef20209d66f741219b00fb896714319503d158a28b0d103d3); /// @dev Internal mapping that holds the balances of every owner (how many NFTs they own). /// @dev Type: mapping (address => uint256) /// @dev Slot: keccak256(bytes("nft.1.balances")) - 1 types.Mapping internal constant $balances = types.Mapping.wrap(0xf9245bc1df90ea86e77b9f2423fe9cc12aa083c8ab9a55e727b285192b30d98a); /// @dev Internal mapping that holds the token approval data. /// @dev Type: mapping (uint256 => address) /// @dev Slot: keccak256(bytes("nft.1.tokenApprovals")) - 1 types.Mapping internal constant $tokenApprovals = types.Mapping.wrap(0x3790264503275ecd52e8f0b419eb5ce016ca8a1f0fbac5a9ede429d0c1732004); /// @dev Internal mapping of operator approvals. /// @dev Type: mapping (address => mapping (address => bool)) /// @dev Slot: keccak256(bytes("nft.1.operatorApprovals")) - 1 uctypes.OperatorApprovalsMapping internal constant $operatorApprovals = uctypes.OperatorApprovalsMapping.wrap(0x6c716a91f6b5f5a0aa2affaf44bd88ea94ec69e363cf1fe9251e00a0fcc6c34e); /// @dev Internal initializer to call when first deploying the contract. // slither-disable-next-line dead-code function initializeNFT(string memory name_, string memory symbol_) internal { _setName(name_); _setSymbol(symbol_); } /// @notice Returns the token uri for the given token id. /// @dev To override /// @param tokenId The token id to query function tokenURI(uint256 tokenId) external view virtual returns (string memory); /// @notice Internal hook happening at each transfer. Not called during mint or burn. Use _onMint and _onBurn instead. /// The hook is called before state transitions are made. /// @dev To override /// @param from The address sending the token /// @param to The address receiving the token /// @param tokenId The token id function _onTransfer(address from, address to, uint256 tokenId) internal virtual; /// @notice Internal hook happening at each mint. /// The hook is called before state transitions are made. /// @dev To override /// @param to The address receiving the token /// @param tokenId The token id function _onMint(address to, uint256 tokenId) internal virtual; /// @notice Internal hook happening at each burn. /// The hook is called before state transitions are made. /// @dev To override /// @param tokenId The token id function _onBurn(uint256 tokenId) internal virtual; /// @inheritdoc INFT function totalSupply() external view returns (uint256) { return $mintCounter.get() - $burnCounter.get(); } /// @inheritdoc IERC721 function balanceOf(address owner) public view virtual override returns (uint256) { LibSanitize.notZeroAddress(owner); return $balances.get()[owner.k()]; } /// @inheritdoc IERC721 function ownerOf(uint256 tokenId) public view virtual override returns (address) { address owner = _ownerOf(tokenId); if (owner == address(0)) { revert InvalidTokenId(tokenId); } return owner; } /// @inheritdoc IERC721Metadata function name() external view virtual returns (string memory) { return string(abi.encodePacked($name.get())); } /// @inheritdoc IERC721Metadata function symbol() external view virtual returns (string memory) { return string(abi.encodePacked($symbol.get())); } /// @inheritdoc IERC165 function supportsInterface(bytes4 interfaceId) external pure returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId; } /// @inheritdoc IERC721 function approve(address to, uint256 tokenId) public virtual override { address owner = _ownerOf(tokenId); if (to == owner) { revert ApprovalToOwner(owner); } if (msg.sender != owner && !isApprovedForAll(owner, msg.sender)) { revert LibErrors.Unauthorized(msg.sender, owner); } _approve(to, owner, tokenId); } /// @inheritdoc IERC721 function getApproved(uint256 tokenId) public view virtual override returns (address) { _requireExists(tokenId); return $tokenApprovals.get()[tokenId].toAddress(); } /// @inheritdoc IERC721 function setApprovalForAll(address operator, bool approved) public virtual override { _setApprovalForAll(msg.sender, operator, approved); } /// @inheritdoc IERC721 function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { return $operatorApprovals.get()[owner][operator]; } /// @inheritdoc IERC721 function transferFrom(address from, address to, uint256 tokenId) public virtual override { if (!_isApprovedOrOwner(msg.sender, tokenId)) { revert LibErrors.Unauthorized(msg.sender, _ownerOf(tokenId)); } _transfer(from, to, tokenId); } /// @inheritdoc IERC721 function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { safeTransferFrom(from, to, tokenId, ""); } /// @inheritdoc IERC721 function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { if (!_isApprovedOrOwner(msg.sender, tokenId)) { revert LibErrors.Unauthorized(msg.sender, _ownerOf(tokenId)); } _safeTransfer(from, to, tokenId, data); } /// @dev Internal utility to set the ERC721 name value. /// @param newName The new name to set // slither-disable-next-line dead-code function _setName(string memory newName) internal { LibSanitize.notEmptyString(newName); $name.set(newName); emit SetName(newName); } /// @dev Internal utility to set the ERC721 symbol value. /// @param newSymbol The new symbol to set // slither-disable-next-line dead-code function _setSymbol(string memory newSymbol) internal { LibSanitize.notEmptyString(newSymbol); $symbol.set(newSymbol); emit SetSymbol(newSymbol); } /// @dev Internal utility to perform a safe transfer (transfer + extra checks on contracts). /// @param from The address sending the token /// @param to The address receiving the token /// @param tokenId The ID of the token /// @param data The extra data provided to contract callback calls function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { _transfer(from, to, tokenId); if (!_checkOnERC721Received(from, to, tokenId, data)) { revert NonERC721ReceiverTransfer(from, to, tokenId, data); } } /// @dev Internal utility to retrieve the owner of the specified token id. /// @param tokenId The token id to lookup /// @return The address of the token owner function _ownerOf(uint256 tokenId) internal view virtual returns (address) { return $owners.get()[tokenId].toAddress(); } /// @dev Internal utility to verify if a token id exists. /// @param tokenId The token id to verify /// @return True if exists function _exists(uint256 tokenId) internal view virtual returns (bool) { return _ownerOf(tokenId) != address(0); } /// @dev Internal utility to check if the specified address is either approved by the owner or the owner for the given token id. /// @param spender The address to verify /// @param tokenId The token id to verify /// @return True if approved or owner function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { address owner = _ownerOf(tokenId); return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); } /// @dev Internal utility to perform a safe mint operation (mint + extra checks on contracts). /// @param to The address receiving the token /// @param tokenId The token id to create /// @param data The xtra data provided to contract callback calls // slither-disable-next-line dead-code function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { _mint(to, tokenId); if (!_checkOnERC721Received(address(0), to, tokenId, data)) { revert NonERC721ReceiverTransfer(address(0), to, tokenId, data); } } /// @dev Internal utility to mint the desired token id. /// @param to The address that receives the token id /// @param tokenId The token id to create // slither-disable-next-line dead-code function _mint(address to, uint256 tokenId) internal virtual { if (to == address(0)) { revert IllegalMintToZero(); } if (_exists(tokenId)) { revert TokenAlreadyMinted(tokenId); } _onMint(to, tokenId); if (_exists(tokenId)) { revert TokenAlreadyMinted(tokenId); } unchecked { // increase owner balance $balances.get()[to.k()] += 1; // increase global mint counter $mintCounter.set($mintCounter.get() + 1); } // set owner $owners.get()[tokenId] = to.v(); emit Transfer(address(0), to, tokenId); } /// @dev Internal utility to burn the desired token id. /// @param tokenId The token id to burn // slither-disable-next-line dead-code function _burn(uint256 tokenId) internal virtual { _requireExists(tokenId); _onBurn(tokenId); _requireExists(tokenId); address from = $owners.get()[tokenId].toAddress(); unchecked { // decrease owner balance $balances.get()[from.k()] -= 1; // increase global burn counter $burnCounter.set($burnCounter.get() + 1); } // clear owner and approvals delete $tokenApprovals.get()[tokenId]; delete $owners.get()[tokenId]; emit Transfer(from, address(0), tokenId); } /// @dev Internal utility to perform a regular transfer of a token. /// @param from The address sending the token /// @param to The address receiving the token /// @param tokenId The tokenId to transfer function _transfer(address from, address to, uint256 tokenId) internal virtual { if (to == address(0)) { revert IllegalTransferToZero(); } if (_ownerOf(tokenId) != from) { revert LibErrors.Unauthorized(_ownerOf(tokenId), from); } _onTransfer(from, to, tokenId); if (_ownerOf(tokenId) != from) { revert LibErrors.Unauthorized(_ownerOf(tokenId), from); } // Clear approvals from the previous owner delete $tokenApprovals.get()[tokenId]; unchecked { $balances.get()[from.k()] -= 1; $balances.get()[to.k()] += 1; } $owners.get()[tokenId] = to.v(); emit Transfer(from, to, tokenId); } /// @dev Internal utility to approve an account for a token id. /// @param to The address receiving the approval /// @param owner The owner of the token id /// @param tokenId The token id function _approve(address to, address owner, uint256 tokenId) internal virtual { $tokenApprovals.get()[tokenId] = to.v(); emit Approval(owner, to, tokenId); } /// @dev Internal utility to approve an account for all tokens of another account. /// @param owner The address owning the tokens /// @param operator The address receiving the approval /// @param approved True if approved, false otherwise function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { if (owner == operator) { revert ApprovalToOwner(owner); } $operatorApprovals.get()[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } /// @dev Internal utility to check and revert if a token doesn't exists. /// @param tokenId The token id to verify function _requireExists(uint256 tokenId) internal view virtual { if (!_exists(tokenId)) { revert InvalidTokenId(tokenId); } } /// @dev Internal utility to perform checks upon transfer in the case of sending to a contract. /// @param from The address sending the token /// @param to The address receiving the token /// @param data The extra data to provide in the case where to is a contract /// @return True if all checks are good // slither-disable-next-line variable-scope,calls-loop,unused-return function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private returns (bool) { if (to.code.length > 0) { try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert NonERC721ReceiverTransfer(from, to, tokenId, data); } else { // slither-disable-next-line assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types/uint256.sol"; /// @title Implementation /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contracts must be used on all implementation contracts. It ensures that the initializers are only callable through the proxy. /// This will brick the implementation and make it unusable directly without using delegatecalls. abstract contract Implementation { using LUint256 for types.Uint256; /// @dev The version number in storage in the initializable contract. /// @dev Slot: keccak256(bytes("initializable.version"))) - 1 types.Uint256 internal constant $initializableVersion = types.Uint256.wrap(0xc4c7f1ccb588f39a9aa57be6cfd798d73912e27b44cfa18e1a5eba7b34e81a76); constructor() { $initializableVersion.set(type(uint256).max); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LBool { // slither-disable-next-line dead-code function get(types.Bool position) internal view returns (bool data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Bool position, bool data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Bool position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CBool { // slither-disable-next-line dead-code function toBytes32(bool val) internal pure returns (bytes32) { return bytes32(toUint256(val)); } // slither-disable-next-line dead-code function toAddress(bool val) internal pure returns (address) { return address(uint160(toUint256(val))); } // slither-disable-next-line dead-code function toUint256(bool val) internal pure returns (uint256 converted) { // slither-disable-next-line assembly assembly { converted := iszero(iszero(val)) } } /// @dev This method should be used to convert a bool to a uint256 when used as a key in a mapping. // slither-disable-next-line dead-code function k(bool val) internal pure returns (uint256) { return toUint256(val); } /// @dev This method should be used to convert a bool to a uint256 when used as a value in a mapping. // slither-disable-next-line dead-code function v(bool val) internal pure returns (uint256) { return toUint256(val); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol) pragma solidity ^0.8.0; /** * @dev Provides a set of functions to operate with Base64 strings. * * _Available since v4.5._ */ library Base64 { /** * @dev Base64 Encoding/Decoding Table */ string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /** * @dev Converts a `bytes` to its Bytes64 `string` representation. */ function encode(bytes memory data) internal pure returns (string memory) { /** * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol */ if (data.length == 0) return ""; // Loads the table into memory string memory table = _TABLE; // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter // and split into 4 numbers of 6 bits. // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up // - `data.length + 2` -> Round up // - `/ 3` -> Number of 3-bytes chunks // - `4 *` -> 4 characters for each chunk string memory result = new string(4 * ((data.length + 2) / 3)); /// @solidity memory-safe-assembly assembly { // Prepare the lookup table (skip the first "length" byte) let tablePtr := add(table, 1) // Prepare result pointer, jump over length let resultPtr := add(result, 32) // Run over the input, 3 bytes at a time for { let dataPtr := data let endPtr := add(data, mload(data)) } lt(dataPtr, endPtr) { } { // Advance 3 bytes dataPtr := add(dataPtr, 3) let input := mload(dataPtr) // To write each character, shift the 3 bytes (18 bits) chunk // 4 times in blocks of 6 bits for each character (18, 12, 6, 0) // and apply logical AND with 0x3F which is the number of // the previous character in the ASCII table prior to the Base64 Table // The result is then added to the table to get the character to write, // and finally write it in the result pointer but with a left shift // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) resultPtr := add(resultPtr, 1) // Advance mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) resultPtr := add(resultPtr, 1) // Advance mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) resultPtr := add(resultPtr, 1) // Advance mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) resultPtr := add(resultPtr, 1) // Advance } // When data `bytes` is not exactly 3 bytes long // it is padded with `=` characters at the end switch mod(mload(data), 3) case 1 { mstore8(sub(resultPtr, 1), 0x3d) mstore8(sub(resultPtr, 2), 0x3d) } case 2 { mstore8(sub(resultPtr, 1), 0x3d) } } return result; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/libs/LibUint256.sol"; import "utils.sol/libs/LibBytes.sol"; import "openzeppelin-contracts/utils/Strings.sol"; /// @title Stringify Library - A library for converting numbers to strings with decimal support library LibStringify { /// @dev Generates a string in memory with the requested count of zeroes /// @param count The number of zeroes to generate /// @return The generated string function generateZeroes(uint256 count) internal pure returns (string memory) { bytes memory zeroes = new bytes(count); for (uint256 idx = 0; idx < count;) { zeroes[idx] = "0"; unchecked { ++idx; } } return string(zeroes); } /// @dev Converts a uint256 to a string with the requested number of decimals /// @param value The value to convert /// @param decimals The number of decimals to include /// @param maxIncludedDecimals The maximum number of decimals to include /// @return The generated string function uintToDecimalString(uint256 value, uint8 decimals, uint8 maxIncludedDecimals) internal pure returns (string memory) { if (value == 0) { return "0"; } bytes memory uintToString = bytes(Strings.toString(value)); if (decimals == 0) { return string(uintToString); } uint256 len = uintToString.length; if (len > decimals) { if (maxIncludedDecimals == 0) { return string(LibBytes.slice(bytes(uintToString), 0, len - decimals)); } uintToString = abi.encodePacked( LibBytes.slice(bytes(uintToString), 0, len - decimals), ".", LibBytes.slice(bytes(uintToString), len - decimals, LibUint256.min(decimals, maxIncludedDecimals)) ); } else { if (maxIncludedDecimals <= decimals - len) { return "0"; } uintToString = abi.encodePacked( "0.", LibBytes.slice( abi.encodePacked(generateZeroes(decimals - len), uintToString), 0, LibUint256.min(decimals, maxIncludedDecimals) ) ); } return string(uintToString); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "./ctypes.sol"; /// @title Ticket Array Custom Type library LTicketArray { function get(ctypes.TicketArray position) internal pure returns (ctypes.Ticket[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "./ctypes.sol"; /// @title Cask Array Custom Type library LCaskArray { function get(ctypes.CaskArray position) internal pure returns (ctypes.Cask[] storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/interfaces/IFixable.sol"; import "./IvPoolSharesReceiver.sol"; import "../ctypes/ctypes.sol"; /// @title Exit Queue Interface /// @author mortimr @ Kiln /// @notice The exit queue stores exit requests until they are filled and claimable interface IvExitQueue is IFixable, IvPoolSharesReceiver { /// @notice Emitted when the stored Pool address is changed /// @param pool The new pool address event SetPool(address pool); /// @notice Emitted when the stored token uri image url is changed /// @param tokenUriImageUrl The new token uri image url event SetTokenUriImageUrl(string tokenUriImageUrl); /// @notice Emitted when the transfer enabled status is changed /// @param enabled The new transfer enabled status event SetTransferEnabled(bool enabled); /// @notice Emitted when the unclaimed funds buffer is changed /// @param unclaimedFunds The new unclaimed funds buffer event SetUnclaimedFunds(uint256 unclaimedFunds); /// @notice Emitted when ether was supplied to the vPool /// @param amount The amount of ETH supplied event SuppliedEther(uint256 amount); /// @notice Emitted when a ticket is created /// @param owner The address of the ticket owner /// @param idx The index of the ticket /// @param id The ID of the ticket /// @param ticket The ticket details event PrintedTicket(address indexed owner, uint32 idx, uint256 id, ctypes.Ticket ticket); /// @notice Emitted when a cask is created /// @param id The ID of the cask /// @param cask The cask details event ReceivedCask(uint32 id, ctypes.Cask cask); /// @notice Emitted when a ticket is claimed against a cask, can happen several times for the same ticket but different casks /// @param ticketId The ID of the ticket /// @param caskId The ID of the cask /// @param amountFilled The amount of shares filled /// @param amountEthFilled The amount of ETH filled /// @param unclaimedEth The amount of ETH that is added to the unclaimed buffer event FilledTicket( uint256 indexed ticketId, uint32 indexed caskId, uint128 amountFilled, uint256 amountEthFilled, uint256 unclaimedEth ); /// @notice Emitted when a ticket is "reminted" and its external id is modified /// @param oldTicketId The old ID of the ticket /// @param newTicketId The new ID of the ticket /// @param ticketIndex The index of the ticket event TicketIdUpdated(uint256 indexed oldTicketId, uint256 indexed newTicketId, uint32 indexed ticketIndex); /// @notice Emitted when a payment is made after a user performed a claim /// @param recipient The address of the recipient /// @param amount The amount of ETH paid event Payment(address indexed recipient, uint256 amount); /// @notice Transfer of tickets is disabled error TransferDisabled(); /// @notice The provided ticket ID is invalid /// @param id The ID of the ticket error InvalidTicketId(uint256 id); /// @notice The provided cask ID is invalid /// @param id The ID of the cask error InvalidCaskId(uint32 id); /// @notice The provided ticket IDs and cask IDs are not the same length error InvalidLengths(); /// @notice The ticket and cask are not associated /// @param ticketId The ID of the ticket /// @param caskId The ID of the cask error TicketNotMatchingCask(uint256 ticketId, uint32 caskId); /// @notice The claim transfer failed /// @param recipient The address of the recipient /// @param rdata The revert data error ClaimTransferFailed(address recipient, bytes rdata); enum ClaimStatus { CLAIMED, PARTIALLY_CLAIMED, SKIPPED } /// @notice Initializes the ExitQueue (proxy pattern) /// @param vpool The address of the associated vPool /// @param newTokenUriImageUrl The token uri image url function initialize(address vpool, string calldata newTokenUriImageUrl) external; /// @notice Returns the token uri image url /// @return The token uri image url function tokenUriImageUrl() external view returns (string memory); /// @notice Returns the transfer enabled status /// @return True if transfers are enabled function transferEnabled() external view returns (bool); /// @notice Returns the unclaimed funds buffer /// @return The unclaimed funds buffer function unclaimedFunds() external view returns (uint256); /// @notice Returns the id of the ticket based on the index /// @param idx The index of the ticket function ticketIdAtIndex(uint32 idx) external view returns (uint256); /// @notice Returns the details about the ticket with the provided ID /// @param id The ID of the ticket /// @return The ticket details function ticket(uint256 id) external view returns (ctypes.Ticket memory); /// @notice Returns the number of tickets /// @return The number of tickets function ticketCount() external view returns (uint256); /// @notice Returns the details about the cask with the provided ID /// @param id The ID of the cask /// @return The cask details function cask(uint32 id) external view returns (ctypes.Cask memory); /// @notice Returns the number of casks /// @return The number of casks function caskCount() external view returns (uint256); /// @notice Resolves the provided tickets to their associated casks or provide resolution error codes /// @dev TICKET_ID_OUT_OF_BOUNDS = -1; /// TICKET_ALREADY_CLAIMED = -2; /// TICKET_PENDING = -3; /// @param ticketIds The IDs of the tickets to resolve /// @return caskIdsOrErrors The IDs of the casks or error codes function resolve(uint256[] memory ticketIds) external view returns (int64[] memory caskIdsOrErrors); /// @notice Adds eth and creates a new cask /// @dev only callbacle by the vPool /// @param shares The amount of shares to cover with the provided eth function feed(uint256 shares) external payable; /// @notice Pulls eth from the unclaimed eth buffer /// @dev Only callable by the vPool /// @param max The maximum amount of eth to pull function pull(uint256 max) external; /// @notice Claims the provided tickets against their associated casks /// @dev To retrieve the list of casks, an off-chain resolve call should be performed /// @param ticketIds The IDs of the tickets to claim /// @param caskIds The IDs of the casks to claim against /// @param maxClaimDepth The maxiumum recursion depth for the claim, 0 for unlimited function claim(uint256[] calldata ticketIds, uint32[] calldata caskIds, uint16 maxClaimDepth) external returns (ClaimStatus[] memory statuses); /// @notice Sets the token uri image inside the returned token uri /// @param newTokenUriImageUrl The new token uri image url function setTokenUriImageUrl(string calldata newTokenUriImageUrl) external; /// @notice Enables or disables transfers of the tickets /// @param value True to allow transfers function setTransferEnabled(bool value) external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/interfaces/IFixable.sol"; import "../ctypes/ctypes.sol"; /// @title Pool Interface /// @author mortimr @ Kiln /// @notice The vPool contract is in charge of pool funds and fund validators from the vFactory interface IvPool is IFixable { /// @notice Emitted at construction time when all contract addresses are set /// @param factory The address of the vFactory contract /// @param withdrawalRecipient The address of the withdrawal recipient contract /// @param execLayerRecipient The address of the execution layer recipient contract /// @param coverageRecipient The address of the coverage recipient contract /// @param oracleAggregator The address of the oracle aggregator contract /// @param exitQueue The address of the exit queue contract event SetContractLinks( address factory, address withdrawalRecipient, address execLayerRecipient, address coverageRecipient, address oracleAggregator, address exitQueue ); /// @notice Emitted when the global validator extra data is changed /// @param extraData New extra data used on validator purchase event SetValidatorGlobalExtraData(string extraData); /// @notice Emitted when a depositor authorization changed /// @param depositor The address of the depositor /// @param allowed True if allowed to deposit event ApproveDepositor(address depositor, bool allowed); /// @notice Emitted when a depositor performs a deposit /// @param sender The transaction sender /// @param amount The deposit amount /// @param mintedShares The amount of shares created event Deposit(address indexed sender, uint256 amount, uint256 mintedShares); /// @notice Emitted when the vPool purchases validators to the vFactory /// @param validators The list of IDs (not BLS Public keys) event PurchasedValidators(uint256[] validators); /// @notice Emitted when new shares are created /// @param account The account receiving the new shares /// @param amount The amount of shares created /// @param totalSupply The new totalSupply value event Mint(address indexed account, uint256 amount, uint256 totalSupply); /// @notice Emitted when shares are burned /// @param burner The account burning shares /// @param amount The amount of burned shares /// @param totalSupply The new totalSupply value event Burn(address burner, uint256 amount, uint256 totalSupply); /// @notice Emitted when shares are transfered /// @param from The account sending the shares /// @param to The account receiving the shares /// @param value The value transfered event Transfer(address indexed from, address indexed to, uint256 value); /// @notice Emitted when shares are approved for a spender /// @param owner The account approving the shares /// @param spender The account receiving the spending rights /// @param value The value of the approval. Max uint256 means infinite (will never decrease) event Approval(address indexed owner, address indexed spender, uint256 value); /// @notice Emitted when shares are voided (action of burning without redeeming anything on purpose) /// @param voider The account voiding the shares /// @param amount The amount of voided shares event VoidedShares(address voider, uint256 amount); /// @notice Emitted when ether is injected into the system (outside of the deposit flow) /// @param injecter The account injecting the ETH /// @param amount The amount of injected ETH event InjectedEther(address injecter, uint256 amount); /// @notice Emitted when the report processing is finished /// @param epoch The epoch number /// @param report The received report structure /// @param traces Internal traces with key figures event ProcessedReport(uint256 indexed epoch, ctypes.ValidatorsReport report, ReportTraces traces); /// @notice Emitted when rewards are distributed to the node operator /// @param operatorTreasury The address receiving the rewards /// @param sharesCount The amount of shares created to pay the rewards /// @param sharesValue The value in ETH of the newly minted shares /// @param totalSupply The updated totalSupply value /// @param totalUnderlyingSupply The updated totalUnderlyingSupply value event DistributedOperatorRewards( address indexed operatorTreasury, uint256 sharesCount, uint256 sharesValue, uint256 totalSupply, uint256 totalUnderlyingSupply ); /// @notice Emitted when the report bounds are updated /// @param maxAPRUpperBound The maximum APR allowed during oracle reports /// @param maxAPRUpperCoverageBoost The APR boost allowed only for coverage funds /// @param maxRelativeLowerBound The max relative delta in underlying supply authorized during losses of funds event SetReportBounds(uint64 maxAPRUpperBound, uint64 maxAPRUpperCoverageBoost, uint64 maxRelativeLowerBound); /// @notice Emitted when the epochs per frame value is updated /// @param epochsPerFrame The new epochs per frame value event SetEpochsPerFrame(uint256 epochsPerFrame); /// @notice Emitted when the consensus layer spec is updated /// @param consensusLayerSpec The new consensus layer spec event SetConsensusLayerSpec(ctypes.ConsensusLayerSpec consensusLayerSpec); /// @notice Emitted when the operator fee is updated /// @param operatorFeeBps The new operator fee value event SetOperatorFee(uint256 operatorFeeBps); /// @notice Emitted when the deposited ether buffer is updated /// @param depositedEthers The new deposited ethers value event SetDepositedEthers(uint256 depositedEthers); /// @notice Emitted when the committed ether buffer is updated /// @param committedEthers The new committed ethers value event SetCommittedEthers(uint256 committedEthers); /// @notice Emitted when the requested exits is updated /// @param newRequestedExits The new requested exits count event SetRequestedExits(uint32 newRequestedExits); /// @notice The balance was too low for the requested operation /// @param account The account trying to perform the operation /// @param currentBalance The current account balance /// @param requiredAmount The amount that was required to perform the operation error BalanceTooLow(address account, uint256 currentBalance, uint256 requiredAmount); /// @notice The allowance was too low for the requested operation /// @param account The account trying to perform the operation /// @param operator The account triggering the operation on behalf of the account /// @param currentApproval The current account approval towards the operator /// @param requiredAmount The amount that was required to perform the operation error AllowanceTooLow(address account, address operator, uint256 currentApproval, uint256 requiredAmount); /// @notice Thrown when approval for an account and spender is already zero. /// @param account The account for which approval was attempted to be set to zero. /// @param spender The spender for which approval was attempted to be set to zero. error ApprovalAlreadyZero(address account, address spender); /// @notice Thrown when there is an error with a share receiver. /// @param err The error message. error ShareReceiverError(string err); /// @notice Thrown when there is no validator available to purchase. error NoValidatorToPurchase(); /// @notice Thrown when the epoch of a report is too old. /// @param epoch The epoch of the report. /// @param expectEpoch The expected epoch for the operation. error EpochTooOld(uint256 epoch, uint256 expectEpoch); /// @notice Thrown when an epoch is not the first epoch of a frame. /// @param epoch The epoch that was not the first epoch of a frame. error EpochNotFrameFirst(uint256 epoch); /// @notice Thrown when an epoch is not final. /// @param epoch The epoch that was not final. /// @param currentTimestamp The current timestamp. /// @param finalTimestamp The final timestamp of the frame. error EpochNotFinal(uint256 epoch, uint256 currentTimestamp, uint256 finalTimestamp); /// @notice Thrown when the validator count is decreasing. /// @param previousValidatorCount The previous validator count. /// @param validatorCount The current validator count. error DecreasingValidatorCount(uint256 previousValidatorCount, uint256 validatorCount); /// @notice Thrown when the stopped validator count is decreasing. /// @param previousStoppedValidatorCount The previous stopped validator count. /// @param stoppedValidatorCount The current stopped validator count. error DecreasingStoppedValidatorCount(uint256 previousStoppedValidatorCount, uint256 stoppedValidatorCount); /// @notice Thrown when the slashed balance sum is decreasing. /// @param reportedSlashedBalanceSum The reported slashed balance sum. /// @param lastReportedSlashedBalanceSum The last reported slashed balance sum. error DecreasingSlashedBalanceSum(uint256 reportedSlashedBalanceSum, uint256 lastReportedSlashedBalanceSum); /// @notice Thrown when the exited balance sum is decreasing. /// @param reportedExitedBalanceSum The reported exited balance sum. /// @param lastReportedExitedBalanceSum The last reported exited balance sum. error DecreasingExitedBalanceSum(uint256 reportedExitedBalanceSum, uint256 lastReportedExitedBalanceSum); /// @notice Thrown when the skimmed balance sum is decreasing. /// @param reportedSkimmedBalanceSum The reported skimmed balance sum. /// @param lastReportedSkimmedBalanceSum The last reported skimmed balance sum. error DecreasingSkimmedBalanceSum(uint256 reportedSkimmedBalanceSum, uint256 lastReportedSkimmedBalanceSum); /// @notice Thrown when the reported validator count is higher than the total activated validators /// @param stoppedValidatorsCount The reported stopped validator count. /// @param maxStoppedValidatorsCount The maximum allowed stopped validator count. error StoppedValidatorCountTooHigh(uint256 stoppedValidatorsCount, uint256 maxStoppedValidatorsCount); /// @notice Thrown when the reported exiting balance exceeds the total validator balance on the cl /// @param exiting The reported exiting balance. /// @param balance The total validator balance on the cl. error ExitingBalanceTooHigh(uint256 exiting, uint256 balance); /// @notice Thrown when the reported validator count is higher than the deposited validator count. /// @param reportedValidatorCount The reported validator count. /// @param depositedValidatorCount The deposited validator count. error ValidatorCountTooHigh(uint256 reportedValidatorCount, uint256 depositedValidatorCount); /// @notice Thrown when the coverage is higher than the loss. /// @param coverage The coverage. /// @param loss The loss. error CoverageHigherThanLoss(uint256 coverage, uint256 loss); /// @notice Thrown when the balance increase exceeds the maximum allowed balance increase. /// @param balanceIncrease The balance increase. /// @param maximumAllowedBalanceIncrease The maximum allowed balance increase. error UpperBoundCrossed(uint256 balanceIncrease, uint256 maximumAllowedBalanceIncrease); /// @notice Thrown when the balance increase exceeds the maximum allowed balance increase or maximum allowed coverage. /// @param balanceIncrease The balance increase. /// @param maximumAllowedBalanceIncrease The maximum allowed balance increase. /// @param maximumAllowedCoverage The maximum allowed coverage. error BoostedBoundCrossed(uint256 balanceIncrease, uint256 maximumAllowedBalanceIncrease, uint256 maximumAllowedCoverage); /// @notice Thrown when the balance decrease exceeds the maximum allowed balance decrease. /// @param balanceDecrease The balance decrease. /// @param maximumAllowedBalanceDecrease The maximum allowed balance decrease. error LowerBoundCrossed(uint256 balanceDecrease, uint256 maximumAllowedBalanceDecrease); /// @notice Thrown when the amount of shares to mint is computed to 0 error InvalidNullMint(); /// @notice Traces emitted at the end of the reporting process. /// @param preUnderlyingSupply The pre-reporting underlying supply. /// @param postUnderlyingSupply The post-reporting underlying supply. /// @param preSupply The pre-reporting supply. /// @param postSupply The post-reporting supply. /// @param newExitedEthers The new exited ethers. /// @param newSkimmedEthers The new skimmed ethers. /// @param exitBoostEthers The exit boost ethers. /// @param exitFedEthers The exit fed ethers. /// @param exitBurnedShares The exit burned shares. /// @param exitingProjection The exiting projection. /// @param baseFulfillableDemand The base fulfillable demand. /// @param extraFulfillableDemand The extra fulfillable demand. /// @param rewards The rewards. Can be negative when there is a loss, but cannot include coverage funds. /// @param delta The delta. Can be negative when there is a loss and include all pulled funds. /// @param increaseLimit The increase limit. /// @param coverageIncreaseLimit The coverage increase limit. /// @param decreaseLimit The decrease limit. /// @param consensusLayerDelta The consensus layer delta. /// @param pulledCoverageFunds The pulled coverage funds. /// @param pulledExecutionLayerRewards The pulled execution layer rewards. /// @param pulledExitQueueUnclaimedFunds The pulled exit queue unclaimed funds. struct ReportTraces { // supplied uint128 preUnderlyingSupply; uint128 postUnderlyingSupply; uint128 preSupply; uint128 postSupply; // new consensus layer funds uint128 newExitedEthers; uint128 newSkimmedEthers; // exit related funds uint128 exitBoostEthers; uint128 exitFedEthers; uint128 exitBurnedShares; uint128 exitingProjection; uint128 baseFulfillableDemand; uint128 extraFulfillableDemand; // rewards int128 rewards; // delta and details about sources of funds int128 delta; uint128 increaseLimit; uint128 coverageIncreaseLimit; uint128 decreaseLimit; int128 consensusLayerDelta; uint128 pulledCoverageFunds; uint128 pulledExecutionLayerRewards; uint128 pulledExitQueueUnclaimedFunds; } /// @notice Initializes the contract with the given parameters. /// @param addrs The addresses of the dependencies (factory, withdrawal recipient, exec layer recipient, /// coverage recipient, oracle aggregator, exit queue). /// @param epochsPerFrame_ The number of epochs per frame. /// @param consensusLayerSpec_ The consensus layer spec. /// @param bounds_ The bounds for reporting. /// @param operatorFeeBps_ The operator fee in basis points. /// @param extraData_ The initial extra data that will be provided on each deposit function initialize( address[6] calldata addrs, uint256 epochsPerFrame_, ctypes.ConsensusLayerSpec calldata consensusLayerSpec_, uint64[3] calldata bounds_, uint256 operatorFeeBps_, string calldata extraData_ ) external; /// @notice Returns the address of the factory contract. /// @return The address of the factory contract. function factory() external view returns (address); /// @notice Returns the address of the execution layer recipient contract. /// @return The address of the execution layer recipient contract. function execLayerRecipient() external view returns (address); /// @notice Returns the address of the coverage recipient contract. /// @return The address of the coverage recipient contract. function coverageRecipient() external view returns (address); /// @notice Returns the address of the withdrawal recipient contract. /// @return The address of the withdrawal recipient contract. function withdrawalRecipient() external view returns (address); /// @notice Returns the address of the oracle aggregator contract. /// @return The address of the oracle aggregator contract. function oracleAggregator() external view returns (address); /// @notice Returns the address of the exit queue contract /// @return The address of the exit queue contract function exitQueue() external view returns (address); /// @notice Returns the current validator global extra data /// @return The validator global extra data value function validatorGlobalExtraData() external view returns (string memory); /// @notice Returns whether the given address is a depositor. /// @param depositorAddress The address to check. /// @return Whether the given address is a depositor. function depositors(address depositorAddress) external view returns (bool); /// @notice Returns the total supply of tokens. /// @return The total supply of tokens. function totalSupply() external view returns (uint256); /// @notice Returns the name of the vPool /// @return The name of the vPool function name() external view returns (string memory); /// @notice Returns the symbol of the vPool /// @return The symbol of the vPool function symbol() external view returns (string memory); /// @notice Returns the decimals of the vPool shares /// @return The decimal count function decimals() external pure returns (uint8); /// @notice Returns the total underlying supply of tokens. /// @return The total underlying supply of tokens. function totalUnderlyingSupply() external view returns (uint256); /// @notice Returns the current ETH/SHARES rate based on the total underlying supply and total supply. /// @return The current rate function rate() external view returns (uint256); /// @notice Returns the current requested exit count /// @return The current requested exit count function requestedExits() external view returns (uint32); /// @notice Returns the balance of the given account. /// @param account The address of the account to check. /// @return The balance of the given account. function balanceOf(address account) external view returns (uint256); /// @notice Returns the allowance of the given spender for the given owner. /// @param owner The owner of the allowance. /// @param spender The spender of the allowance. /// @return The allowance of the given spender for the given owner. function allowance(address owner, address spender) external view returns (uint256); /// @notice Returns the details about the held ethers /// @return The structure of ethers inside the contract function ethers() external view returns (ctypes.Ethers memory); /// @notice Returns an array of the IDs of purchased validators. /// @return An array of the IDs of purchased validators. function purchasedValidators() external view returns (uint256[] memory); /// @notice Returns the ID of the purchased validator at the given index. /// @param idx The index of the validator. /// @return The ID of the purchased validator at the given index. function purchasedValidatorAtIndex(uint256 idx) external view returns (uint256); /// @notice Returns the total number of purchased validators. /// @return The total number of purchased validators. function purchasedValidatorCount() external view returns (uint256); /// @notice Returns the last epoch. /// @return The last epoch. function lastEpoch() external view returns (uint256); /// @notice Returns the last validator report that was processed /// @return The last report structure. function lastReport() external view returns (ctypes.ValidatorsReport memory); /// @notice Returns the total amount in ETH covered by the contract. /// @return The total amount in ETH covered by the contract. function totalCovered() external view returns (uint256); /// @notice Returns the number of epochs per frame. /// @return The number of epochs per frame. function epochsPerFrame() external view returns (uint256); /// @notice Returns the consensus layer spec. /// @return The consensus layer spec. function consensusLayerSpec() external pure returns (ctypes.ConsensusLayerSpec memory); /// @notice Returns the report bounds. /// @return maxAPRUpperBound The maximum APR for the upper bound. /// @return maxAPRUpperCoverageBoost The maximum APR for the upper bound with coverage boost. /// @return maxRelativeLowerBound The maximum relative lower bound. function reportBounds() external view returns (uint64 maxAPRUpperBound, uint64 maxAPRUpperCoverageBoost, uint64 maxRelativeLowerBound); /// @notice Returns the operator fee. /// @return The operator fee. function operatorFee() external view returns (uint256); /// @notice Returns whether the given epoch is valid. /// @param epoch The epoch to check. /// @return Whether the given epoch is valid. function isValidEpoch(uint256 epoch) external view returns (bool); /// @notice Reverts if given epoch is invalid, with an explicit custom error based on the issue /// @param epoch The epoch to check. function onlyValidEpoch(uint256 epoch) external view; /// @notice Allows or disallows the given depositor to deposit. /// @param depositorAddress The address of the depositor. /// @param allowed Whether the depositor is allowed to deposit. function allowDepositor(address depositorAddress, bool allowed) external; /// @notice Transfers the given amount of shares to the given address. /// @param to The address to transfer the shares to. /// @param amount The amount of shares to transfer. /// @param data Additional data for the transfer. /// @return Whether the transfer was successful. function transferShares(address to, uint256 amount, bytes calldata data) external returns (bool); /// @notice Increases the allowance for the given spender by the given amount. /// @param spender The spender to increase the allowance for. /// @param amount The amount to increase the allowance by. /// @return Whether the increase was successful. function increaseAllowance(address spender, uint256 amount) external returns (bool); /// @notice Decreases the allowance of a spender by the given amount. /// @param spender The address of the spender. /// @param amount The amount to decrease the allowance by. /// @return Whether the allowance was successfully decreased. function decreaseAllowance(address spender, uint256 amount) external returns (bool); /// @notice Voids the allowance of a spender. /// @param spender The address of the spender. /// @return Whether the allowance was successfully voided. function voidAllowance(address spender) external returns (bool); /// @notice Transfers shares from one account to another. /// @param from The address of the account to transfer shares from. /// @param to The address of the account to transfer shares to. /// @param amount The amount of shares to transfer. /// @param data Optional data to include with the transaction. /// @return Whether the transfer was successful. function transferSharesFrom(address from, address to, uint256 amount, bytes calldata data) external returns (bool); /// @notice Deposits ether into the contract. /// @return The number of shares minted on deposit function deposit() external payable returns (uint256); /// @notice Purchases the maximum number of validators allowed. /// @param max The maximum number of validators to purchase. function purchaseValidators(uint256 max) external; /// @notice Sets the operator fee. /// @param operatorFeeBps The new operator fee, in basis points. function setOperatorFee(uint256 operatorFeeBps) external; /// @notice Sets the number of epochs per frame. /// @param newEpochsPerFrame The new number of epochs per frame. function setEpochsPerFrame(uint256 newEpochsPerFrame) external; /// @notice Sets the consensus layer spec. /// @param consensusLayerSpec_ The new consensus layer spec. function setConsensusLayerSpec(ctypes.ConsensusLayerSpec calldata consensusLayerSpec_) external; /// @notice Sets the global validator extra data /// @param extraData The new extra data to use function setValidatorGlobalExtraData(string calldata extraData) external; /// @notice Sets the bounds for reporting. /// @param maxAPRUpperBound The maximum APR for the upper bound. /// @param maxAPRUpperCoverageBoost The maximum APR for the upper coverage boost. /// @param maxRelativeLowerBound The maximum relative value for the lower bound. function setReportBounds(uint64 maxAPRUpperBound, uint64 maxAPRUpperCoverageBoost, uint64 maxRelativeLowerBound) external; /// @notice Injects ether into the contract. function injectEther() external payable; /// @notice Voids the given amount of shares. /// @param amount The amount of shares to void. function voidShares(uint256 amount) external; /// @notice Reports the validator data for the given epoch. /// @param rprt The consensus layer report to process function report(ctypes.ValidatorsReport calldata rprt) external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/interfaces/IAdministrable.sol"; import "utils.sol/interfaces/IDepositor.sol"; import "utils.sol/interfaces/IFixable.sol"; /// @title Factory Interface /// @author mortimr @ Kiln /// @notice The vFactory contract is in charge of depositing validators to the consensus layer interface IvFactory is IAdministrable, IDepositor, IFixable { /// @notice The provided array is empty error EmptyArray(); /// @notice The provided arrays do not have matching lengths error InvalidArrayLengths(); /// @notice The withdrawal attempt was made on a validator that collected no funds error EmptyWithdrawalRecipient(); /// @notice The provided key concatenation is empty /// @param index The index of the invalid key concatenation in the calldata parameters error EmptyKeyPayload(uint256 index); /// @notice The provided validator id is invalid /// @param id The invalid id error InvalidValidatorId(uint256 id); /// @notice The provided key concatenation is invalid /// @param index The index of the invalid key concatenation in the calldata parameters error InvalidKeyPayload(uint256 index); /// @notice The provided indexes array if empty /// @param index The index of the invalid index array in the calldata parameters error EmptyIndexesArray(uint256 index); /// @notice The provided indexes array is unsorted /// @param index The index of the invalid index array in the calldata parameters error UnsortedIndexArray(uint256 index); /// @notice The withdrawal call performed on the minimal recipient reverted /// @param rdata The resulting error return data error MinimalRecipientExecutionError(bytes rdata); /// @notice The provided withdrawal channel is invalid /// @param withdrawalChannel The invalid withdrawal channel error InvalidWithdrawalChannel(bytes32 withdrawalChannel); /// @notice The provided message value in ether is invalid /// @param received The provided amount /// @param expected The expected amount error InvalidMessageValue(uint256 received, uint256 expected); /// @notice The requested validator count is too high /// @param requested The count of validators requested /// @param available The count of available validators error NotEnoughValidators(uint256 requested, uint256 available); /// @notice The provided validator index is out of bounds /// @param index The indexes array index in the calldata /// @param validatorIndex The invalid validator index error ValidatorIndexOutOfBounds(uint256 index, uint256 validatorIndex); /// @notice A funded validator removal was attempted /// @param index The indexes array index in the calldata /// @param validatorIndex The funded validator index error FundedValidatorRemovalAttempt(uint256 index, uint256 validatorIndex); /// @notice Error raised when the requested total exits on a custom channel is higher than the total funded count /// @param withdrawalChannel The withdrawal channel /// @param requestedTotal The total requested exits /// @param maxFundedCount The count of funded validators on the channel error ExitTotalTooHigh(bytes32 withdrawalChannel, uint32 requestedTotal, uint32 maxFundedCount); /// @notice Error raised when the requested limit on a withdrawal channel is higher than the validators count. /// @param withdrawalChannel The withdrawal channel /// @param limit The limit requested /// @param validatorCount The count of validators on the channel error LimitExceededValidatorCount(bytes32 withdrawalChannel, uint256 limit, uint256 validatorCount); /// @notice Emitted when the minimal recipient implementation is set /// @param minimalRecipientImplementation The address of the implementation event SetMinimalRecipientImplementation(address minimalRecipientImplementation); /// @notice Emitted when hatcher registry is set /// @param hatcherRegistry The address of the hatcher registry event SetHatcherRegistry(address hatcherRegistry); /// @notice Emitted when the operator changed /// @param operator The new operator address event ChangedOperator(address operator); /// @notice Emitted when the treasury changed /// @param treasury The new treasury address event ChangedTreasury(address treasury); /// @notice Emitted when an exit request was made /// @param withdrawalChannel The withdrawal channel that received the exit request /// @param publicKey The public key of the validator that requested the exit /// @param id The id of the validator that requested the exit event ExitValidator(bytes32 indexed withdrawalChannel, bytes publicKey, uint256 id); /// @notice Emitted when the owner of a validator is changed /// @param id The id of the validator /// @param owner The new owner address event SetValidatorOwner(uint256 indexed id, address owner); /// @notice Emitted when the metadata of the vFactory is changed /// @param name The operator name /// @param url The operator shared url /// @param iconUrl The operator icon event SetMetadata(string name, string url, string iconUrl); /// @notice Emitted when a depositor authorization changed /// @param depositor The address of the depositor /// @param wc The withdrawal channel /// @param allowed True if allowed to deposit event ApproveDepositor(address indexed depositor, bytes32 indexed wc, bool allowed); /// @notice Emitted when new keys are added to a withdrawal channel /// @param withdrawalChannel The withdrawal channel that received new keys /// @param keys The keys that were added event AddedValidators(bytes32 indexed withdrawalChannel, bytes keys); /// @notice Emitted when the staking limit has been changed for a withdrawal channel /// @param withdrawalChannel The withdrawal channel that had its limit updated /// @param limit The new staking limit of the withdrawal channel event UpdatedLimit(bytes32 indexed withdrawalChannel, uint256 limit); /// @notice Emitted when funds have been withdrawn from a validator withdrawal recipient /// @param id The id of the validator /// @param recipient The address receiving the funds /// @param value The value that was withdrawn event Withdraw(uint256 indexed id, address recipient, uint256 value); /// @notice Emitted when a validator extra data is changed /// @param id The id of the validator /// @param extraData The new extra data value event SetValidatorExtraData(uint256 indexed id, string extraData); /// @notice Emitted when a validator fee recipient is changed /// @param id The id of the validator /// @param feeRecipient The new fee recipient address event SetValidatorFeeRecipient(uint256 indexed id, address feeRecipient); /// @notice Emitted when keys are requested on a withdrawal channel /// @param withdrawalChannel The withdrawal channel where keys have been requested /// @param total The expect total key count of the channel event ValidatorRequest(bytes32 indexed withdrawalChannel, uint256 total); /// @notice Emitted when a channel exit request is above the funded count /// @param funded The count of funded validators on the channel /// @param requestedTotal The total requested exits event ExitRequestAboveFunded(uint32 funded, uint32 requestedTotal); /// @notice Emitted when a validator key has been removed from a withdrawal channel /// @param withdrawalChannel The withdrawal channel where the key has been removed /// @param publicKey The public key that has been removed /// @param validatorIndex The index of the removed validator key event RemovedValidator(bytes32 indexed withdrawalChannel, bytes publicKey, uint256 validatorIndex); /// @notice Emitted when a validator key is funded /// @param withdrawalChannel The withdrawal channel where the validator got funded /// @param depositor The address of the depositor bringing the funds for the validator /// @param withdrawalAddress The address of the withdrawal recipient /// @param publicKey The BLS Public key of the funded validator /// @param id The unique id of the validator /// @param validatorIndex The index of the funded validator in the withdrawal channel event FundedValidator( bytes32 indexed withdrawalChannel, address indexed depositor, address indexed withdrawalAddress, bytes publicKey, uint256 id, uint256 validatorIndex ); /// @notice Emitted when the total exit for a custom withdrawal channel is changed /// @param withdrawalChannel The withdrawal channel where the exit count is changed /// @param totalExited The new total exited value event SetExitTotal(bytes32 indexed withdrawalChannel, uint32 totalExited); /// @notice Emitted when the last edit is after the snapshot (when editing the limit). The snapshot limit is staled. /// @param withdrawalChannel The withdrawal channel /// @param limit The limit requested event LastEditAfterSnapshot(bytes32 indexed withdrawalChannel, uint256 limit); /// @notice Initializes the vFactory /// @dev Can only be called once /// @param depositContract Address of the deposit contract to use /// @param admin Address of the contract admin /// @param operator_ Address of the contract operator /// @param treasury_ Address of the treasury /// @param minimalRecipientImplementation Address used by the clones as implementation for the withdrawal recipients /// @param hatcherRegistry Contract holding the hatcher registry function initialize( string memory name, address depositContract, address admin, address operator_, address treasury_, address minimalRecipientImplementation, address hatcherRegistry ) external; /// @notice Retrieve the current operator address /// @return The operator address function operator() external view returns (address); /// @notice Retrieve the current treasury address /// @return The treasury address function treasury() external view returns (address); /// @notice Retrieve the depositor status /// @param depositor Address to verify /// @param wc Withdrawal channel to verify /// @return Status of the depositor function depositors(address depositor, bytes32 wc) external view returns (bool); /// @notice Retrieve the details of a validator by its unique id /// @param id ID of the validator /// @return found True if the ID matches a validator /// @return funded True if the validator is funded /// @return wc The withdrawal channel of the validator /// @return index The index of the validator in the withdrawal channel /// @return publicKey The BLS public key of the validator /// @return signature The BLS signature of the validator /// @return owner The address owning the validator /// @return withdrawalRecipient The address where the withdrawal rewards will go to /// @return feeRecipient The address where the execution layer fees are expected to go to function validator(uint256 id) external view returns ( bool found, bool funded, bytes32 wc, uint256 index, bytes memory publicKey, bytes memory signature, address owner, address withdrawalRecipient, address feeRecipient ); /// @notice Retrieve the details of a validator by its unique id /// @param ids IDs of the validators /// @return Public keys of the provided IDs function publicKeys(uint256[] calldata ids) external view returns (bytes[] memory); /// @notice Retrieve the details of a key in a withdrawalChannel /// @param wc The withdrawal channel the key is stored in /// @param index The index of the key in the withdrawal channel /// @return found True if there's a key at the given index in the withdrawal channel /// @return publicKey The BLS public key of the validator /// @return signature The BLS signature of the validator /// @return withdrawalRecipient The address where the withdrawal rewards will go to function key(bytes32 wc, uint256 index) external view returns (bool found, bytes memory publicKey, bytes memory signature, address withdrawalRecipient); /// @notice Retrieve the number of validators owned by an account in a specific withdrawal channel /// @param wc The withdrawal channel to inspect /// @param owner The account owning the validators /// @return The number of owned validators in the withdrawal channel function balance(bytes32 wc, address owner) external view returns (uint256); /// @notice Retrieve the key details of the withdrawal channel /// @param wc The withdrawal channel to inspect /// @return total The total count of deposited keys /// @return limit The staking limit of the channel /// @return funded The count of funded validators function withdrawalChannel(bytes32 wc) external view returns (uint32 total, uint32 limit, uint32 funded); /// @notice Retrieve the operator public metadata /// @return name The operator name. Cannot be empty. /// @return url The operator shared url. Can be empty. /// @return iconUrl The operator icon url function metadata() external view returns (string memory name, string memory url, string memory iconUrl); /// @notice Retrieve the withdrawal address for the specified public key /// @dev This is only useful on the null withdrawal channel where the vFactory spawns /// minimal clones deterministically as the withdrawal recipients of each validator. /// @param publicKey The BLS Public Key of the validator /// @return The address where the minimal clone will be deployed to retrieve the consensus layer rewards function withdrawalAddress(bytes calldata publicKey) external view returns (address); /// @notice Retrieve the count of fundable validators on a withdrawal channel /// @param wc The withdrawal channel to inspect /// @return The count of fundable validators function availableValidators(bytes32 wc) external view returns (uint256); /// @notice Changes the operator address /// @dev Only callable by the admin /// @param newOperator New operator address function setOperator(address newOperator) external; /// @notice Changes the operator public metadata /// @param name The operator name. Cannot be empty. /// @param url The operator shared url. Can be empty. /// @param iconUrl The operator icon url function setMetadata(string calldata name, string calldata url, string calldata iconUrl) external; /// @notice Add or remove depositor /// @dev Callable by the admin of the factory or the nexus /// @param depositor The address to add or remove /// @param wc The withdrawal channel to add or remove the depositor from /// @param allowed True to allow as depositor function allowDepositor(address depositor, bytes32 wc, bool allowed) external; /// @notice Emits an event signaling a request in keys on a specific withdrawal channel /// @param wc The withdrawal channel to perform the request on /// @param amount The amount of keys that should be added to the channel function request(bytes32 wc, uint256 amount) external; /// @notice Adds keys to several withdrawal channels /// @dev It's expected that the provided withdrawalChannels and _keys have the same length. /// For each withdrawalChannel, a concatenation of [S1,P1,S2,P2...,SN,PN] is expected. /// S = BLS Signature and P = BLS Public Key. Signature should come first in each pair. /// @param withdrawalChannels The list of withdrawal channels to add keys on /// @param keys The list of key concatenations to add to the withdrawal channels function addValidators(bytes32[] calldata withdrawalChannels, bytes[] calldata keys) external; /// @notice Removes keys from several withdrawal channels /// @dev It's expected that the provided withdrawalChannels and _indexes have the same length. /// For each withdrawalChannel, an array of indexes is expected. These indexes should be sorted in descending order. /// Each array should not contain any duplicate index. /// @param withdrawalChannels The list of withdrawal channels to add keys on /// @param indexes The list of lists of indexes to remove from the withdrawal channels function removeValidators(bytes32[] calldata withdrawalChannels, uint256[][] calldata indexes) external; /// @notice Modifies the staking limits of several withdrawal channels /// @dev It's expected that the provided withdrawalChannels, _limits and _snapshots have the same length /// For each withdrawalChannel, a new limit is provided alongside a snapshot block number. /// If the new limit value decreases the current one, no extra check if performed and the limit is decreased. /// If the new limit value increases the current one, we check that no key modifictions have been done after /// the provided snapshot block. If it's the case, we don't update the limit and we don't revert, we simply /// emit an event alerting that the last key edition happened after the snapshot. Otherwise the limit is increased. /// @param withdrawalChannels The list of withdrawal channels to update the limits /// @param limits The list of new staking limits values /// @param snapshots The list of block snapshots to respect if the limit is increased function approve(bytes32[] calldata withdrawalChannels, uint256[] calldata limits, uint256[] calldata snapshots) external; /// @notice Deposits _count validators on the provided withdrawal channel /// @dev This call reverts if the count of available keys is too low on the withdrawal channel /// @param wc The withdrawal channel to fund keys on /// @param count The amount of keys to fund /// @param feeRecipient The fee recipient to set all the funded keys on /// @param owner The address owning the validators /// @param extradata The extra data to transmit to the node operator /// @return An array of unique IDs identifying the funded validators function deposit(bytes32 wc, uint256 count, address feeRecipient, address owner, string calldata extradata) external payable returns (uint256[] memory); /// @notice Changes the fee recipient of several validators /// @dev Only callable by the owner of the validators /// @param ids The list of validator IDs /// @param newFeeRecipient The new fee recipient address function setFeeRecipient(uint256[] calldata ids, address newFeeRecipient) external; /// @notice Changes the owner of several validators /// @dev Only callable by the owner of the validators /// @param ids The list of validator IDs /// @param newOwner The new owner address function setOwner(uint256[] calldata ids, address newOwner) external; /// @notice Changes the extradata of several validators /// @dev Only callable by the owner of the validators /// @param ids The list of validator IDs /// @param newExtradata The new validator extra data function setExtraData(uint256[] calldata ids, string calldata newExtradata) external; /// @notice Emits an exit request event for several validators /// @dev Only callable by the owner of the validators /// @param ids The list of validator IDs function exit(uint256[] calldata ids) external; /// @notice Perform a consensus layer withdrawal on several validators /// @dev Only callable by the owner of the validators and on funded validators from the null withdrawal channel /// @param ids The list of validator IDs /// @param recipient The address that should receive the funds, that implements the WithdrawRecipientLike interface function withdraw(uint256[] calldata ids, address recipient) external; /// @notice Requests a new total exited validator count for the withdrawal recipient calling the method /// @dev This endpoint is callable by any address, it's up to the operator to properly filter the calls /// based on existing withdrawal channels only. /// @param totalExited The new total exited validator count for the withdrawal channel /// @return The new total exited validator count for the withdrawal channel function exitTotal(uint32 totalExited) external returns (uint32); } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Fixable Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Fixable contract can be used on cubs to expose a safe noop to force a fix. interface IFixable { /// @notice Noop method to force a global fix to be applied. function fix() external; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.0; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; /// @notice Library Address - Address slot utilities. library LAddress { // slither-disable-next-line dead-code, assembly function get(types.Address position) internal view returns (address data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Address position, address data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Address position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CAddress { // slither-disable-next-line dead-code function toUint256(address val) internal pure returns (uint256) { return uint256(uint160(val)); } // slither-disable-next-line dead-code function toBytes32(address val) internal pure returns (bytes32) { return bytes32(uint256(uint160(val))); } // slither-disable-next-line dead-code function toBool(address val) internal pure returns (bool converted) { // slither-disable-next-line assembly assembly { converted := gt(val, 0) } } /// @notice This method should be used to convert an address to a uint256 when used as a key in a mapping. // slither-disable-next-line dead-code function k(address val) internal pure returns (uint256) { return toUint256(val); } /// @notice This method should be used to convert an address to a uint256 when used as a value in a mapping. // slither-disable-next-line dead-code function v(address val) internal pure returns (uint256) { return toUint256(val); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LString { struct StringStorage { string value; } // slither-disable-next-line dead-code function get(types.String position) internal view returns (string memory) { StringStorage storage ss; // slither-disable-next-line assembly assembly { ss.slot := position } return ss.value; } // slither-disable-next-line dead-code function set(types.String position, string memory value) internal { StringStorage storage ss; // slither-disable-next-line assembly assembly { ss.slot := position } ss.value = value; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LMapping { // slither-disable-next-line dead-code function get(types.Mapping position) internal pure returns (mapping(uint256 => uint256) storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./LibErrors.sol"; import "./LibConstant.sol"; /// @title Lib Sanitize /// @dev This library helps sanitizing inputs. library LibSanitize { /// @dev Internal utility to sanitize an address and ensure its value is not 0. /// @param addressValue The address to verify // slither-disable-next-line dead-code function notZeroAddress(address addressValue) internal pure { if (addressValue == address(0)) { revert LibErrors.InvalidZeroAddress(); } } /// @dev Internal utility to sanitize an uint256 value and ensure its value is not 0. /// @param value The value to verify // slither-disable-next-line dead-code function notNullValue(uint256 value) internal pure { if (value == 0) { revert LibErrors.InvalidNullValue(); } } /// @dev Internal utility to sanitize a bps value and ensure it's <= 100%. /// @param value The bps value to verify // slither-disable-next-line dead-code function notInvalidBps(uint256 value) internal pure { if (value > LibConstant.BASIS_POINTS_MAX) { revert LibErrors.InvalidBPSValue(); } } /// @dev Internal utility to sanitize a string value and ensure it's not empty. /// @param stringValue The string value to verify // slither-disable-next-line dead-code function notEmptyString(string memory stringValue) internal pure { if (bytes(stringValue).length == 0) { revert LibErrors.InvalidEmptyString(); } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types/uint256.sol"; /// @title Initializable /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contracts helps upgradeable contracts handle an internal /// version value to prevent initializer replays. abstract contract Initializable { using LUint256 for types.Uint256; /// @notice The version has been initialized. /// @param version The version number initialized /// @param cdata The calldata used for the call event Initialized(uint256 version, bytes cdata); /// @notice The init modifier has already been called on the given version number. /// @param version The provided version number /// @param currentVersion The stored version number error AlreadyInitialized(uint256 version, uint256 currentVersion); /// @dev The version number in storage. /// @dev Slot: keccak256(bytes("initializable.version"))) - 1 types.Uint256 internal constant $version = types.Uint256.wrap(0xc4c7f1ccb588f39a9aa57be6cfd798d73912e27b44cfa18e1a5eba7b34e81a76); /// @dev The modifier to use on initializers. /// @dev Do not provide _version dynamically, make sure the value is hard-coded each /// time the modifier is used. /// @param _version The version to initialize // slither-disable-next-line incorrect-modifier modifier init(uint256 _version) { if (_version == $version.get()) { $version.set(_version + 1); emit Initialized(_version, msg.data); _; } else { revert AlreadyInitialized(_version, $version.get()); } } } // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; import "openzeppelin-contracts/token/ERC721/extensions/IERC721Metadata.sol"; /// @title NFT /// @author mortimr @ Kiln /// @notice NFT contract using utils.sol storage format. interface INFT is IERC721Metadata { /// @notice Emitted when name is changed. /// @param name The new ERC721 contract name event SetName(string name); /// @notice Emitted when symbol is changed. /// @param symbol The new ERC721 contract symbol event SetSymbol(string symbol); /// @notice Thrown when the token is already minted when it shouldn't. /// @param tokenId The id of the already existing token error TokenAlreadyMinted(uint256 tokenId); /// @notice Thrown when a mint operation to address zero is attempted. error IllegalMintToZero(); /// @notice Thrown when a transfer operation to address zero is attempted. error IllegalTransferToZero(); /// @notice Thrown when approval to self is made. /// @param owner Address attempting approval to self error ApprovalToOwner(address owner); /// @notice Thrown when provided token id is invalid. /// @param tokenId The invalid token id error InvalidTokenId(uint256 tokenId); /// @notice Thrown when the receiving contract is not able to receive the token. /// @param from The address sending the token /// @param to The address (contract) receiving the token and failing to properly receive it /// @param tokenId The token id /// @param data The extra data provided to the call error NonERC721ReceiverTransfer(address from, address to, uint256 tokenId, bytes data); /// @notice Throw when an nft transfer was attempted while the nft is frozen. /// NFTs get frozen for ever once the exit request is made. /// NFTs get frozen for 6 hours when a withdrawal is made. /// @param tokenId The frozen token id /// @param currentTimestamp The timestamp where the transfer was attempted /// @param freezeTimestamp The timestamp until which the token is frozen error IllegalTransferWhileFrozen(uint256 tokenId, uint256 currentTimestamp, uint256 freezeTimestamp); /// @notice Retrieve the total count of validator created with this contract. /// @return The total count of NFT validators of this contract function totalSupply() external view returns (uint256); } //SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; import "./uctypes.sol"; /// @title Operator Approvals Custom Type library LOperatorApprovalsMapping { function get(uctypes.OperatorApprovalsMapping position) internal pure returns (mapping(address => mapping(address => bool)) storage data) { // slither-disable-next-line assembly assembly { data.slot := position } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "./types.sol"; library LUint256 { // slither-disable-next-line dead-code function get(types.Uint256 position) internal view returns (uint256 data) { // slither-disable-next-line assembly assembly { data := sload(position) } } // slither-disable-next-line dead-code function set(types.Uint256 position, uint256 data) internal { // slither-disable-next-line assembly assembly { sstore(position, data) } } // slither-disable-next-line dead-code function del(types.Uint256 position) internal { // slither-disable-next-line assembly assembly { sstore(position, 0) } } } library CUint256 { // slither-disable-next-line dead-code function toBytes32(uint256 val) internal pure returns (bytes32) { return bytes32(val); } // slither-disable-next-line dead-code function toAddress(uint256 val) internal pure returns (address) { return address(uint160(val)); } // slither-disable-next-line dead-code function toBool(uint256 val) internal pure returns (bool) { return (val & 1) == 1; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @dev Library holding bytes32 custom types // slither-disable-next-line naming-convention library types { type Uint256 is bytes32; type Address is bytes32; type Bytes32 is bytes32; type Bool is bytes32; type String is bytes32; type Mapping is bytes32; type Array is bytes32; } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; import "prb-math/PRBMath.sol"; library LibUint256 { // slither-disable-next-line dead-code function min(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly // slither-disable-next-line assembly assembly { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @custom:author Vectorized/solady#58681e79de23082fd3881a76022e0842f5c08db8 // slither-disable-next-line dead-code function max(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly // slither-disable-next-line assembly assembly { z := xor(x, mul(xor(x, y), gt(y, x))) } } // slither-disable-next-line dead-code function mulDiv(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) { return PRBMath.mulDiv(a, b, c); } // slither-disable-next-line dead-code function ceil(uint256 num, uint256 den) internal pure returns (uint256) { return (num / den) + (num % den > 0 ? 1 : 0); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Lib Bytes /// @dev This library helps manipulating bytes. library LibBytes { /// @dev The length overflows an uint. error SliceOverflow(); /// @dev The slice is outside of the initial bytes bounds. error SliceOutOfBounds(); /// @dev Slices the provided bytes. /// @param bytes_ Bytes to slice /// @param start The starting index of the slice /// @param length The length of the slice /// @return The slice of _bytes starting at _start of length _length // slither-disable-next-line dead-code function slice(bytes memory bytes_, uint256 start, uint256 length) internal pure returns (bytes memory) { unchecked { if (length + 31 < length) { revert SliceOverflow(); } } if (bytes_.length < start + length) { revert SliceOutOfBounds(); } bytes memory tempBytes; // slither-disable-next-line assembly 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 // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol) pragma solidity ^0.8.0; import "./math/Math.sol"; import "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { bytes16 private constant _SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toString(int256 value) internal pure returns (string memory) { return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value)))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, Math.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return keccak256(bytes(a)) == keccak256(bytes(b)); } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; import "utils.sol/libs/LibPublicKey.sol"; import "utils.sol/libs/LibSignature.sol"; /// @title Custom Types // slither-disable-next-line naming-convention library ctypes { /// @notice Structure representing a validator in the factory /// @param publicKey The public key of the validator /// @param signature The signature used for the deposit /// @param feeRecipient The address receiving the exec layer fees struct Validator { LibPublicKey.PublicKey publicKey; LibSignature.Signature signature; address feeRecipient; } /// @notice Structure representing a withdrawal channel in the factory /// @param validators The validators in the channel /// @param lastEdit The last time the channel was edited (in blocks) /// @param limit The staking limit of the channel. Always <= validators.length /// @param funded The amount of funded validators in the channel struct WithdrawalChannel { Validator[] validators; uint256 lastEdit; uint32 limit; uint32 funded; } /// @notice Structure representing a deposit in the factory /// @param index The index of the deposit in the withdrawal channel /// @param withdrawalChannel The withdrawal channel of the validator /// @param owner The owner of the deposited validator struct Deposit { uint256 index; bytes32 withdrawalChannel; address owner; } /// @notice Structure representing the operator metadata in the factory /// @param name The name of the operator /// @param url The url of the operator /// @param iconUrl The icon url of the operator struct Metadata { string name; string url; string iconUrl; } /// @notice Structure representing the global consensus layer spec held in the global consensus layer spec holder /// @param genesisTimestamp The timestamp of the genesis of the consensus layer (slot 0 timestamp) /// @param epochsUntilFinal The number of epochs until a block is considered final by the vsuite /// @param slotsPerEpoch The number of slots per epoch (32 on mainnet) /// @param secondsPerSlot The number of seconds per slot (12 on mainnet) struct ConsensusLayerSpec { uint64 genesisTimestamp; uint64 epochsUntilFinal; uint64 slotsPerEpoch; uint64 secondsPerSlot; } /// @notice Structure representing the report bounds held in the pools /// @param maxAPRUpperBound The maximum APR upper bound, representing the maximum increase in underlying balance checked at each oracle report /// @param maxAPRUpperCoverageBoost The maximum APR upper coverage boost, representing the additional increase allowed when pulling coverage funds /// @param maxRelativeLowerBound The maximum relative lower bound, representing the maximum decrease in underlying balance checked at each oracle report struct ReportBounds { uint64 maxAPRUpperBound; uint64 maxAPRUpperCoverageBoost; uint64 maxRelativeLowerBound; } /// @notice Structure representing the consensus layer report submitted by oracle members /// @param balanceSum sum of all the balances of all validators that have been activated by the vPool /// this means that as long as the validator was activated, no matter its current status, its balance is taken /// into account /// @param exitedSum sum of all the ether that has been exited by the validators that have been activated by the vPool /// to compute this value, we look for withdrawal events inside the block bodies that have happened at an epoch /// that is greater or equal to the withdrawable epoch of a validator purchased by the pool /// when we detect any, we take min(amount,32 eth) into account as exited balance /// @param skimmedSum sum of all the ether that has been skimmed by the validators that have been activated by the vPool /// similar to the exitedSum, we look for withdrawal events. If the epochs is lower than the withdrawable epoch /// we take into account the full withdrawal amount, otherwise we take amount - min(amount, 32 eth) into account /// @param slashedSum sum of all the ether that has been slashed by the validators that have been activated by the vPool /// to compute this value, we look for validators that are of have been in the slashed state /// then we take the balance of the validator at the epoch prior to its slashing event /// we then add the delta between this old balance and the current balance (or balance just before withdrawal) /// @param exiting amount of currently exiting eth, that will soon hit the withdrawal recipient /// this value is computed by taking the balance of any validator in the exit or slashed state or after /// @param maxExitable maximum amount that can get requested for exits during report processing /// this value is determined by the oracle. its calculation logic can be updated but all members need to agree and reach /// consensus on the new calculation logic. Its role is to control the rate at which exit requests are performed /// @param maxCommittable maximum amount that can get committed for deposits during report processing /// positive value means commit happens before possible exit boosts, negative after /// similar to the mexExitable, this value is determined by the oracle. its calculation logic can be updated but all /// members need to agree and reach consensus on the new calculation logic. Its role is to control the rate at which /// deposit are made. Committed funds are funds that are always a multiple of 32 eth and that cannot be used for /// anything else than purchasing validator, as opposed to the deposited funds that can still be used to fuel the /// exit queue in some cases. /// @param epoch epoch at which the report was crafter /// @param activatedCount current count of validators that have been activated by the vPool /// no matter the current state of the validator, if it has been activated, it has to be accounted inside this value /// @param stoppedCount current count of validators that have been stopped (being in the exit queue, exited or slashed) struct ValidatorsReport { uint128 balanceSum; uint128 exitedSum; uint128 skimmedSum; uint128 slashedSum; uint128 exiting; uint128 maxExitable; int256 maxCommittable; uint64 epoch; uint32 activatedCount; uint32 stoppedCount; } /// @notice Structure representing the ethers held in the pools /// @param deposited The amount of deposited ethers, that can either be used to boost exits or get committed /// @param committed The amount of committed ethers, that can only be used to purchase validators struct Ethers { uint128 deposited; uint128 committed; } /// @notice Structure representing a ticket in the exit queue /// @param position The position of the ticket in the exit queue (equal to the position + size of the previous ticket) /// @param size The size of the ticket in the exit queue (in pool shares) /// @param maxExitable The maximum amount of ethers that can be exited by the ticket owner (no more rewards in the exit queue, losses are still mutualized) struct Ticket { uint128 position; uint128 size; uint128 maxExitable; } /// @notice Structure representing a cask in the exit queue. This entity is created by the pool upon oracle reports, when exit liquidity is available to feed the exit queue /// @param position The position of the cask in the exit queue (equal to the position + size of the previous cask) /// @param size The size of the cask in the exit queue (in pool shares) /// @param value The value of the cask in the exit queue (in ethers) struct Cask { uint128 position; uint128 size; uint128 value; } type DepositMapping is bytes32; type WithdrawalChannelMapping is bytes32; type BalanceMapping is bytes32; type MetadataStruct is bytes32; type ConsensusLayerSpecStruct is bytes32; type ReportBoundsStruct is bytes32; type ApprovalsMapping is bytes32; type ValidatorsReportStruct is bytes32; type EthersStruct is bytes32; type TicketArray is bytes32; type CaskArray is bytes32; type FactoryDepositorMapping is bytes32; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity 0.8.17; /// @title Pool Shares Receiver Interface /// @author mortimr @ Kiln /// @notice Interface that needs to be implemented for a contract to be able to receive shares interface IvPoolSharesReceiver { /// @notice Callback used by the vPool to notify contracts of shares being transfered /// @param operator The address of the operator of the transfer /// @param from The address sending the funds /// @param amount The amount of shares received /// @param data The attached data /// @return selector Should return its own selector if everything went well function onvPoolSharesReceived(address operator, address from, uint256 amount, bytes memory data) external returns (bytes4 selector); } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Administrable Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice This contract provides all the utilities to handle the administration and its transfer. interface IAdministrable { /// @notice The admin address has been changed. /// @param admin The new admin address event SetAdmin(address admin); /// @notice The pending admin address has been changed. /// @param pendingAdmin The pending admin has been changed event SetPendingAdmin(address pendingAdmin); /// @notice Retrieve the admin address. /// @return adminAddress The admin address function admin() external view returns (address adminAddress); /// @notice Retrieve the pending admin address. /// @return pendingAdminAddress The pending admin address function pendingAdmin() external view returns (address pendingAdminAddress); /// @notice Propose a new admin. /// @dev Only callable by the admin /// @param _newAdmin The new admin to propose function transferAdmin(address _newAdmin) external; /// @notice Accept an admin transfer. /// @dev Only callable by the pending admin function acceptAdmin() external; } // SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; /// @title Depositor Interface /// @author mortimr @ Kiln /// @dev Unstructured Storage Friendly /// @notice The Depositor contract adds deposit capabilities to easily fund /// validators and activate them on the Consensus Layer. interface IDepositor { /// @notice The provided public key is not 48 bytes long. error InvalidPublicKeyLength(); /// @notice The provided signature is not 96 bytes long. error InvalidSignatureLength(); /// @notice The balance is too low for the deposit. error InvalidDepositSize(); /// @notice An error occured during the deposit. error DepositError(); /// @notice The deposit contract address has been updated. /// @param depositContract The new deposit contract address event SetDepositContract(address depositContract); /// @notice Retrieve the deposit contract address. /// @return depositContractAddress The deposit contract address function depositContract() external view returns (address depositContractAddress); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibErrors { error Unauthorized(address account, address expected); error InvalidZeroAddress(); error InvalidNullValue(); error InvalidBPSValue(); error InvalidEmptyString(); } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibConstant { /// @dev The basis points value representing 100%. uint256 internal constant BASIS_POINTS_MAX = 10_000; /// @dev The size of a deposit to activate a validator. uint256 internal constant DEPOSIT_SIZE = 32 ether; /// @dev The minimum freeze timeout before freeze is active. uint256 internal constant MINIMUM_FREEZE_TIMEOUT = 100 days; /// @dev Address used to represent ETH when an address is required to identify an asset. address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) pragma solidity ^0.8.0; import "../IERC721.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension * @dev See https://eips.ethereum.org/EIPS/eip-721 */ interface IERC721Metadata is IERC721 { /** * @dev Returns the token collection name. */ function name() external view returns (string memory); /** * @dev Returns the token collection symbol. */ function symbol() external view returns (string memory); /** * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. */ function tokenURI(uint256 tokenId) external view returns (string memory); } //SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; /// @title Uitls Custom Types // slither-disable-next-line naming-convention library uctypes { type OperatorApprovalsMapping is bytes32; } // SPDX-License-Identifier: Unlicense pragma solidity >=0.8.4; /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivFixedPointOverflow(uint256 prod1); /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivOverflow(uint256 prod1, uint256 denominator); /// @notice Emitted when one of the inputs is type(int256).min. error PRBMath__MulDivSignedInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows int256. error PRBMath__MulDivSignedOverflow(uint256 rAbs); /// @notice Emitted when the input is MIN_SD59x18. error PRBMathSD59x18__AbsInputTooSmall(); /// @notice Emitted when ceiling a number overflows SD59x18. error PRBMathSD59x18__CeilOverflow(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__DivInputTooSmall(); /// @notice Emitted when one of the intermediary unsigned results overflows SD59x18. error PRBMathSD59x18__DivOverflow(uint256 rAbs); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathSD59x18__ExpInputTooBig(int256 x); /// @notice Emitted when the input is greater than 192. error PRBMathSD59x18__Exp2InputTooBig(int256 x); /// @notice Emitted when flooring a number underflows SD59x18. error PRBMathSD59x18__FloorUnderflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format overflows SD59x18. error PRBMathSD59x18__FromIntOverflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format underflows SD59x18. error PRBMathSD59x18__FromIntUnderflow(int256 x); /// @notice Emitted when the product of the inputs is negative. error PRBMathSD59x18__GmNegativeProduct(int256 x, int256 y); /// @notice Emitted when multiplying the inputs overflows SD59x18. error PRBMathSD59x18__GmOverflow(int256 x, int256 y); /// @notice Emitted when the input is less than or equal to zero. error PRBMathSD59x18__LogInputTooSmall(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__MulInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__MulOverflow(uint256 rAbs); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__PowuOverflow(uint256 rAbs); /// @notice Emitted when the input is negative. error PRBMathSD59x18__SqrtNegativeInput(int256 x); /// @notice Emitted when the calculating the square root overflows SD59x18. error PRBMathSD59x18__SqrtOverflow(int256 x); /// @notice Emitted when addition overflows UD60x18. error PRBMathUD60x18__AddOverflow(uint256 x, uint256 y); /// @notice Emitted when ceiling a number overflows UD60x18. error PRBMathUD60x18__CeilOverflow(uint256 x); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathUD60x18__ExpInputTooBig(uint256 x); /// @notice Emitted when the input is greater than 192. error PRBMathUD60x18__Exp2InputTooBig(uint256 x); /// @notice Emitted when converting a basic integer to the fixed-point format format overflows UD60x18. error PRBMathUD60x18__FromUintOverflow(uint256 x); /// @notice Emitted when multiplying the inputs overflows UD60x18. error PRBMathUD60x18__GmOverflow(uint256 x, uint256 y); /// @notice Emitted when the input is less than 1. error PRBMathUD60x18__LogInputTooSmall(uint256 x); /// @notice Emitted when the calculating the square root overflows UD60x18. error PRBMathUD60x18__SqrtOverflow(uint256 x); /// @notice Emitted when subtraction underflows UD60x18. error PRBMathUD60x18__SubUnderflow(uint256 x, uint256 y); /// @dev Common mathematical functions used in both PRBMathSD59x18 and PRBMathUD60x18. Note that this shared library /// does not always assume the signed 59.18-decimal fixed-point or the unsigned 60.18-decimal fixed-point /// representation. When it does not, it is explicitly mentioned in the NatSpec documentation. library PRBMath { /// STRUCTS /// struct SD59x18 { int256 value; } struct UD60x18 { uint256 value; } /// STORAGE /// /// @dev How many trailing decimals can be represented. uint256 internal constant SCALE = 1e18; /// @dev Largest power of two divisor of SCALE. uint256 internal constant SCALE_LPOTD = 262144; /// @dev SCALE inverted mod 2^256. uint256 internal constant SCALE_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281; /// FUNCTIONS /// /// @notice Calculates the binary exponent of x using the binary fraction method. /// @dev Has to use 192.64-bit fixed-point numbers. /// See https://ethereum.stackexchange.com/a/96594/24693. /// @param x The exponent as an unsigned 192.64-bit fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function exp2(uint256 x) internal pure returns (uint256 result) { unchecked { // Start from 0.5 in the 192.64-bit fixed-point format. result = 0x800000000000000000000000000000000000000000000000; // Multiply the result by root(2, 2^-i) when the bit at position i is 1. None of the intermediary results overflows // because the initial result is 2^191 and all magic factors are less than 2^65. if (x & 0x8000000000000000 > 0) { result = (result * 0x16A09E667F3BCC909) >> 64; } if (x & 0x4000000000000000 > 0) { result = (result * 0x1306FE0A31B7152DF) >> 64; } if (x & 0x2000000000000000 > 0) { result = (result * 0x1172B83C7D517ADCE) >> 64; } if (x & 0x1000000000000000 > 0) { result = (result * 0x10B5586CF9890F62A) >> 64; } if (x & 0x800000000000000 > 0) { result = (result * 0x1059B0D31585743AE) >> 64; } if (x & 0x400000000000000 > 0) { result = (result * 0x102C9A3E778060EE7) >> 64; } if (x & 0x200000000000000 > 0) { result = (result * 0x10163DA9FB33356D8) >> 64; } if (x & 0x100000000000000 > 0) { result = (result * 0x100B1AFA5ABCBED61) >> 64; } if (x & 0x80000000000000 > 0) { result = (result * 0x10058C86DA1C09EA2) >> 64; } if (x & 0x40000000000000 > 0) { result = (result * 0x1002C605E2E8CEC50) >> 64; } if (x & 0x20000000000000 > 0) { result = (result * 0x100162F3904051FA1) >> 64; } if (x & 0x10000000000000 > 0) { result = (result * 0x1000B175EFFDC76BA) >> 64; } if (x & 0x8000000000000 > 0) { result = (result * 0x100058BA01FB9F96D) >> 64; } if (x & 0x4000000000000 > 0) { result = (result * 0x10002C5CC37DA9492) >> 64; } if (x & 0x2000000000000 > 0) { result = (result * 0x1000162E525EE0547) >> 64; } if (x & 0x1000000000000 > 0) { result = (result * 0x10000B17255775C04) >> 64; } if (x & 0x800000000000 > 0) { result = (result * 0x1000058B91B5BC9AE) >> 64; } if (x & 0x400000000000 > 0) { result = (result * 0x100002C5C89D5EC6D) >> 64; } if (x & 0x200000000000 > 0) { result = (result * 0x10000162E43F4F831) >> 64; } if (x & 0x100000000000 > 0) { result = (result * 0x100000B1721BCFC9A) >> 64; } if (x & 0x80000000000 > 0) { result = (result * 0x10000058B90CF1E6E) >> 64; } if (x & 0x40000000000 > 0) { result = (result * 0x1000002C5C863B73F) >> 64; } if (x & 0x20000000000 > 0) { result = (result * 0x100000162E430E5A2) >> 64; } if (x & 0x10000000000 > 0) { result = (result * 0x1000000B172183551) >> 64; } if (x & 0x8000000000 > 0) { result = (result * 0x100000058B90C0B49) >> 64; } if (x & 0x4000000000 > 0) { result = (result * 0x10000002C5C8601CC) >> 64; } if (x & 0x2000000000 > 0) { result = (result * 0x1000000162E42FFF0) >> 64; } if (x & 0x1000000000 > 0) { result = (result * 0x10000000B17217FBB) >> 64; } if (x & 0x800000000 > 0) { result = (result * 0x1000000058B90BFCE) >> 64; } if (x & 0x400000000 > 0) { result = (result * 0x100000002C5C85FE3) >> 64; } if (x & 0x200000000 > 0) { result = (result * 0x10000000162E42FF1) >> 64; } if (x & 0x100000000 > 0) { result = (result * 0x100000000B17217F8) >> 64; } if (x & 0x80000000 > 0) { result = (result * 0x10000000058B90BFC) >> 64; } if (x & 0x40000000 > 0) { result = (result * 0x1000000002C5C85FE) >> 64; } if (x & 0x20000000 > 0) { result = (result * 0x100000000162E42FF) >> 64; } if (x & 0x10000000 > 0) { result = (result * 0x1000000000B17217F) >> 64; } if (x & 0x8000000 > 0) { result = (result * 0x100000000058B90C0) >> 64; } if (x & 0x4000000 > 0) { result = (result * 0x10000000002C5C860) >> 64; } if (x & 0x2000000 > 0) { result = (result * 0x1000000000162E430) >> 64; } if (x & 0x1000000 > 0) { result = (result * 0x10000000000B17218) >> 64; } if (x & 0x800000 > 0) { result = (result * 0x1000000000058B90C) >> 64; } if (x & 0x400000 > 0) { result = (result * 0x100000000002C5C86) >> 64; } if (x & 0x200000 > 0) { result = (result * 0x10000000000162E43) >> 64; } if (x & 0x100000 > 0) { result = (result * 0x100000000000B1721) >> 64; } if (x & 0x80000 > 0) { result = (result * 0x10000000000058B91) >> 64; } if (x & 0x40000 > 0) { result = (result * 0x1000000000002C5C8) >> 64; } if (x & 0x20000 > 0) { result = (result * 0x100000000000162E4) >> 64; } if (x & 0x10000 > 0) { result = (result * 0x1000000000000B172) >> 64; } if (x & 0x8000 > 0) { result = (result * 0x100000000000058B9) >> 64; } if (x & 0x4000 > 0) { result = (result * 0x10000000000002C5D) >> 64; } if (x & 0x2000 > 0) { result = (result * 0x1000000000000162E) >> 64; } if (x & 0x1000 > 0) { result = (result * 0x10000000000000B17) >> 64; } if (x & 0x800 > 0) { result = (result * 0x1000000000000058C) >> 64; } if (x & 0x400 > 0) { result = (result * 0x100000000000002C6) >> 64; } if (x & 0x200 > 0) { result = (result * 0x10000000000000163) >> 64; } if (x & 0x100 > 0) { result = (result * 0x100000000000000B1) >> 64; } if (x & 0x80 > 0) { result = (result * 0x10000000000000059) >> 64; } if (x & 0x40 > 0) { result = (result * 0x1000000000000002C) >> 64; } if (x & 0x20 > 0) { result = (result * 0x10000000000000016) >> 64; } if (x & 0x10 > 0) { result = (result * 0x1000000000000000B) >> 64; } if (x & 0x8 > 0) { result = (result * 0x10000000000000006) >> 64; } if (x & 0x4 > 0) { result = (result * 0x10000000000000003) >> 64; } if (x & 0x2 > 0) { result = (result * 0x10000000000000001) >> 64; } if (x & 0x1 > 0) { result = (result * 0x10000000000000001) >> 64; } // We're doing two things at the same time: // // 1. Multiply the result by 2^n + 1, where "2^n" is the integer part and the one is added to account for // the fact that we initially set the result to 0.5. This is accomplished by subtracting from 191 // rather than 192. // 2. Convert the result to the unsigned 60.18-decimal fixed-point format. // // This works because 2^(191-ip) = 2^ip / 2^191, where "ip" is the integer part "2^n". result *= SCALE; result >>= (191 - (x >> 64)); } } /// @notice Finds the zero-based index of the first one in the binary representation of x. /// @dev See the note on msb in the "Find First Set" Wikipedia article https://en.wikipedia.org/wiki/Find_first_set /// @param x The uint256 number for which to find the index of the most significant bit. /// @return msb The index of the most significant bit as an uint256. function mostSignificantBit(uint256 x) internal pure returns (uint256 msb) { if (x >= 2**128) { x >>= 128; msb += 128; } if (x >= 2**64) { x >>= 64; msb += 64; } if (x >= 2**32) { x >>= 32; msb += 32; } if (x >= 2**16) { x >>= 16; msb += 16; } if (x >= 2**8) { x >>= 8; msb += 8; } if (x >= 2**4) { x >>= 4; msb += 4; } if (x >= 2**2) { x >>= 2; msb += 2; } if (x >= 2**1) { // No need to shift x any more. msb += 1; } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv. /// /// Requirements: /// - The denominator cannot be zero. /// - The result must fit within uint256. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The multiplicand as an uint256. /// @param y The multiplier as an uint256. /// @param denominator The divisor as an uint256. /// @return result The result as an uint256. function mulDiv( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 result) { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { unchecked { result = prod0 / denominator; } return result; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (prod1 >= denominator) { revert PRBMath__MulDivOverflow(prod1, denominator); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. unchecked { // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 lpotdod = denominator & (~denominator + 1); assembly { // Divide denominator by lpotdod. denominator := div(denominator, lpotdod) // Divide [prod1 prod0] by lpotdod. prod0 := div(prod0, lpotdod) // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one. lpotdod := add(div(sub(0, lpotdod), lpotdod), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * lpotdod; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /// @notice Calculates floor(x*y÷1e18) with full precision. /// /// @dev Variant of "mulDiv" with constant folding, i.e. in which the denominator is always 1e18. Before returning the /// final result, we add 1 if (x * y) % SCALE >= HALF_SCALE. Without this, 6.6e-19 would be truncated to 0 instead of /// being rounded to 1e-18. See "Listing 6" and text above it at https://accu.org/index.php/journals/1717. /// /// Requirements: /// - The result must fit within uint256. /// /// Caveats: /// - The body is purposely left uncommented; see the NatSpec comments in "PRBMath.mulDiv" to understand how this works. /// - It is assumed that the result can never be type(uint256).max when x and y solve the following two equations: /// 1. x * y = type(uint256).max * SCALE /// 2. (x * y) % SCALE >= SCALE / 2 /// /// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number. /// @param y The multiplier as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function mulDivFixedPoint(uint256 x, uint256 y) internal pure returns (uint256 result) { uint256 prod0; uint256 prod1; assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } if (prod1 >= SCALE) { revert PRBMath__MulDivFixedPointOverflow(prod1); } uint256 remainder; uint256 roundUpUnit; assembly { remainder := mulmod(x, y, SCALE) roundUpUnit := gt(remainder, 499999999999999999) } if (prod1 == 0) { unchecked { result = (prod0 / SCALE) + roundUpUnit; return result; } } assembly { result := add( mul( or( div(sub(prod0, remainder), SCALE_LPOTD), mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, SCALE_LPOTD), SCALE_LPOTD), 1)) ), SCALE_INVERSE ), roundUpUnit ) } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev An extension of "mulDiv" for signed numbers. Works by computing the signs and the absolute values separately. /// /// Requirements: /// - None of the inputs can be type(int256).min. /// - The result must fit within int256. /// /// @param x The multiplicand as an int256. /// @param y The multiplier as an int256. /// @param denominator The divisor as an int256. /// @return result The result as an int256. function mulDivSigned( int256 x, int256 y, int256 denominator ) internal pure returns (int256 result) { if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) { revert PRBMath__MulDivSignedInputTooSmall(); } // Get hold of the absolute values of x, y and the denominator. uint256 ax; uint256 ay; uint256 ad; unchecked { ax = x < 0 ? uint256(-x) : uint256(x); ay = y < 0 ? uint256(-y) : uint256(y); ad = denominator < 0 ? uint256(-denominator) : uint256(denominator); } // Compute the absolute value of (x*y)÷denominator. The result must fit within int256. uint256 rAbs = mulDiv(ax, ay, ad); if (rAbs > uint256(type(int256).max)) { revert PRBMath__MulDivSignedOverflow(rAbs); } // Get the signs of x, y and the denominator. uint256 sx; uint256 sy; uint256 sd; assembly { sx := sgt(x, sub(0, 1)) sy := sgt(y, sub(0, 1)) sd := sgt(denominator, sub(0, 1)) } // XOR over sx, sy and sd. This is checking whether there are one or three negative signs in the inputs. // If yes, the result should be negative. result = sx ^ sy ^ sd == 0 ? -int256(rAbs) : int256(rAbs); } /// @notice Calculates the square root of x, rounding down. /// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The uint256 number for which to calculate the square root. /// @return result The result as an uint256. function sqrt(uint256 x) internal pure returns (uint256 result) { if (x == 0) { return 0; } // Set the initial guess to the least power of two that is greater than or equal to sqrt(x). uint256 xAux = uint256(x); result = 1; if (xAux >= 0x100000000000000000000000000000000) { xAux >>= 128; result <<= 64; } if (xAux >= 0x10000000000000000) { xAux >>= 64; result <<= 32; } if (xAux >= 0x100000000) { xAux >>= 32; result <<= 16; } if (xAux >= 0x10000) { xAux >>= 16; result <<= 8; } if (xAux >= 0x100) { xAux >>= 8; result <<= 4; } if (xAux >= 0x10) { xAux >>= 4; result <<= 2; } if (xAux >= 0x8) { result <<= 1; } // The operations can never overflow because the result is max 2^127 when it enters this block. unchecked { result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; // Seven iterations should be enough uint256 roundedDownResult = x / result; return result >= roundedDownResult ? roundedDownResult : result; } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol) pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { enum Rounding { Down, // Toward negative infinity Up, // Toward infinity Zero // Toward zero } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds up instead * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.0; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibPublicKey { // slither-disable-next-line unused-state uint256 constant PUBLIC_KEY_LENGTH = 48; // slither-disable-next-line unused-state bytes constant PADDING = hex"00000000000000000000000000000000"; struct PublicKey { bytes32 A; bytes16 B; } // slither-disable-next-line dead-code function toBytes(PublicKey memory publicKey) internal pure returns (bytes memory) { return abi.encodePacked(publicKey.A, publicKey.B); } // slither-disable-next-line dead-code function fromBytes(bytes memory publicKey) internal pure returns (PublicKey memory ret) { publicKey = bytes.concat(publicKey, PADDING); (bytes32 A, bytes32 B_prime) = abi.decode(publicKey, (bytes32, bytes32)); bytes16 B = bytes16(uint128(uint256(B_prime) >> 128)); ret.A = A; ret.B = B; } } // SPDX-License-Identifier: BUSL-1.1 // SPDX-FileCopyrightText: 2023 Kiln <[email protected]> // // ██╗ ██╗██╗██╗ ███╗ ██╗ // ██║ ██╔╝██║██║ ████╗ ██║ // █████╔╝ ██║██║ ██╔██╗ ██║ // ██╔═██╗ ██║██║ ██║╚██╗██║ // ██║ ██╗██║███████╗██║ ╚████║ // ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═══╝ // pragma solidity >=0.8.17; library LibSignature { // slither-disable-next-line unused-state uint256 constant SIGNATURE_LENGTH = 96; struct Signature { bytes32 A; bytes32 B; bytes32 C; } // slither-disable-next-line dead-code function toBytes(Signature memory signature) internal pure returns (bytes memory) { return abi.encodePacked(signature.A, signature.B, signature.C); } // slither-disable-next-line dead-code function fromBytes(bytes memory signature) internal pure returns (Signature memory ret) { (ret) = abi.decode(signature, (Signature)); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; import "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }