Transaction Hash:
Block:
22679048 at Jun-11-2025 04:51:59 AM +UTC
Transaction Fee:
0.000057310222960348 ETH
$0.14
Gas Used:
31,474 Gas / 1.820875102 Gwei
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x22Ea8Bc2...3814B1235 |
8.896372994936294315 Eth
Nonce: 6
|
8.896315684713333967 Eth
Nonce: 7
| 0.000057310222960348 | ||
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 10.261874037229864906 Eth | 10.261886012862046124 Eth | 0.000011975632181218 |
Execution Trace
TUPProxy.a9059cbb( )

-
Native20.a9059cbb( )
File 1 of 2: TUPProxy
File 2 of 2: Native20
// 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 2: 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; }