Overview
xDAI Balance
xDAI Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 25 from a total of 1,623 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Exec Transaction... | 38449198 | 2 mins ago | IN | 0 xDAI | 0.00068078 | ||||
Exec Transaction... | 38449011 | 18 mins ago | IN | 0 xDAI | 0.00054807 | ||||
Exec Transaction... | 38449011 | 18 mins ago | IN | 0 xDAI | 0.00068083 | ||||
Exec Transaction... | 38448787 | 37 mins ago | IN | 0 xDAI | 0.00068059 | ||||
Exec Transaction... | 38448729 | 42 mins ago | IN | 0 xDAI | 0.00068074 | ||||
Exec Transaction... | 38448670 | 47 mins ago | IN | 0 xDAI | 0.0006808 | ||||
Exec Transaction... | 38448522 | 1 hr ago | IN | 0 xDAI | 0.00054802 | ||||
Exec Transaction... | 38448522 | 1 hr ago | IN | 0 xDAI | 0.00068076 | ||||
Exec Transaction... | 38448232 | 1 hr ago | IN | 0 xDAI | 0.00068072 | ||||
Exec Transaction... | 38448196 | 1 hr ago | IN | 0 xDAI | 0.00068072 | ||||
Exec Transaction... | 38448125 | 1 hr ago | IN | 0 xDAI | 0.00068064 | ||||
Exec Transaction... | 38447936 | 1 hr ago | IN | 0 xDAI | 0.00068072 | ||||
Exec Transaction... | 38447924 | 1 hr ago | IN | 0 xDAI | 0.00068073 | ||||
Exec Transaction... | 38447724 | 2 hrs ago | IN | 0 xDAI | 0.00068071 | ||||
Exec Transaction... | 38447547 | 2 hrs ago | IN | 0 xDAI | 0.00068085 | ||||
Exec Transaction... | 38447464 | 2 hrs ago | IN | 0 xDAI | 0.00068082 | ||||
Exec Transaction... | 38447441 | 2 hrs ago | IN | 0 xDAI | 0.00068085 | ||||
Exec Transaction... | 38447346 | 2 hrs ago | IN | 0 xDAI | 0.00068079 | ||||
Exec Transaction... | 38447264 | 2 hrs ago | IN | 0 xDAI | 0.00068073 | ||||
Exec Transaction... | 38446933 | 3 hrs ago | IN | 0 xDAI | 0.00068066 | ||||
Exec Transaction... | 38446921 | 3 hrs ago | IN | 0 xDAI | 0.0006807 | ||||
Exec Transaction... | 38446814 | 3 hrs ago | IN | 0 xDAI | 0.0006807 | ||||
Exec Transaction... | 38446779 | 3 hrs ago | IN | 0 xDAI | 0.00068084 | ||||
Exec Transaction... | 38446755 | 3 hrs ago | IN | 0 xDAI | 0.0006806 | ||||
Exec Transaction... | 38446404 | 4 hrs ago | IN | 0 xDAI | 0.00068073 |
Latest 1 internal transaction
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
36085490 | 141 days ago | Contract Creation | 0 xDAI |
Loading...
Loading
Minimal Proxy Contract for 0xb7397c218766ebe6a1a634df523a1a7e412e67ea
Contract Name:
HoprNodeManagementModule
Compiler Version
v0.8.19+commit.7dd6d404
Contract Source Code (Solidity)
/** *Submitted for verification at gnosisscan.io on 2023-08-30 */ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; /** * This contract follows the principle of `zodiac/core/Module.sol` * but implement differently in order to overwrite functionalities */ /** * @title Enum - Collection of enums used in Safe contracts. * @author Richard Meissner - @rmeissner */ abstract contract Enum { enum Operation { Call, DelegateCall } } /** * @title Interface for Avatar (Safe) * Adapted from `IAvatar.sol` at commit 8a77e7b224af8004bd9f2ff4e2919642e93ffd85, which * was audited https://github.com/gnosis/zodiac/tree/master/audits * Added an additional function `getOwners()` */ interface IAvatar { function getOwners() external view returns (address[] memory); /// @dev Enables a module on the avatar. /// @notice Can only be called by the avatar. /// @notice Modules should be stored as a linked list. /// @notice Must emit EnabledModule(address module) if successful. /// @param module Module to be enabled. function enableModule(address module) external; /// @dev Disables a module on the avatar. /// @notice Can only be called by the avatar. /// @notice Must emit DisabledModule(address module) if successful. /// @param prevModule Address that pointed to the module to be removed in the linked list /// @param module Module to be removed. function disableModule(address prevModule, address module) external; /// @dev Allows a Module to execute a transaction. /// @notice Can only be called by an enabled module. /// @notice Must emit ExecutionFromModuleSuccess(address module) if successful. /// @notice Must emit ExecutionFromModuleFailure(address module) if unsuccessful. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction: 0 == call, 1 == delegate call. function execTransactionFromModule( address to, uint256 value, bytes memory data, Enum.Operation operation ) external returns (bool success); /// @dev Allows a Module to execute a transaction and return data /// @notice Can only be called by an enabled module. /// @notice Must emit ExecutionFromModuleSuccess(address module) if successful. /// @notice Must emit ExecutionFromModuleFailure(address module) if unsuccessful. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction: 0 == call, 1 == delegate call. function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) external returns (bool success, bytes memory returnData); /// @dev Returns if an module is enabled /// @return True if the module is enabled function isModuleEnabled(address module) external view returns (bool); /// @dev Returns array of modules. /// @param start Start of the page. /// @param pageSize Maximum number of modules that should be returned. /// @return array Array of modules. /// @return next Start of the next page. function getModulesPaginated( address start, uint256 pageSize ) external view returns (address[] memory array, address next); } // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol) // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) /** * @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 IERC1822ProxiableUpgradeable { /** * @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); } // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol) // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol) /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeaconUpgradeable { /** * @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); } // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol) /** * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. * * _Available since v4.8.3._ */ interface IERC1967Upgradeable { /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Emitted when the beacon is changed. */ event BeaconUpgraded(address indexed beacon); } // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @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.8.0/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); } } } // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. /** * @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`, `uint256`._ * _Available since v4.9 for `string`, `bytes`._ */ library StorageSlotUpgradeable { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } struct StringSlot { string value; } struct BytesSlot { bytes value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { /// @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 } } /** * @dev Returns an `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` representation of the string storage pointer `store`. */ function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } /** * @dev Returns an `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. */ function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } } // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in * case an upgrade adds a module that needs to be initialized. * * For example: * * [.hljs-theme-light.nopadding] * ```solidity * contract MyToken is ERC20Upgradeable { * function initialize() initializer public { * __ERC20_init("MyToken", "MTK"); * } * } * * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { * function initializeV2() reinitializer(2) public { * __ERC20Permit_init("MyToken"); * } * } * ``` * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() { * _disableInitializers(); * } * ``` * ==== */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. * @custom:oz-retyped-from bool */ uint8 private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Triggered when the contract has been initialized or reinitialized. */ event Initialized(uint8 version); /** * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, * `onlyInitializing` functions can be used to initialize parent contracts. * * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a * constructor. * * Emits an {Initialized} event. */ modifier initializer() { bool isTopLevelCall = !_initializing; require( (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), "Initializable: contract is already initialized" ); _initialized = 1; if (isTopLevelCall) { _initializing = true; } _; if (isTopLevelCall) { _initializing = false; emit Initialized(1); } } /** * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be * used to initialize parent contracts. * * A reinitializer may be used after the original initialization step. This is essential to configure modules that * are added through upgrades and that require initialization. * * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` * cannot be nested. If one is invoked in the context of another, execution will revert. * * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in * a contract, executing them in the right order is up to the developer or operator. * * WARNING: setting the version to 255 will prevent any future reinitialization. * * Emits an {Initialized} event. */ modifier reinitializer(uint8 version) { require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); _initialized = version; _initializing = true; _; _initializing = false; emit Initialized(version); } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { require(_initializing, "Initializable: contract is not initializing"); _; } /** * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized * to any version. It is recommended to use this to lock implementation contracts that are designed to be called * through proxies. * * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { require(!_initializing, "Initializable: contract is initializing"); if (_initialized != type(uint8).max) { _initialized = type(uint8).max; emit Initialized(type(uint8).max); } } /** * @dev Returns the highest version that has been initialized. See {reinitializer}. */ function _getInitializedVersion() internal view returns (uint8) { return _initialized; } /** * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. */ function _isInitializing() internal view returns (bool) { return _initializing; } } /** * @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 ERC1967UpgradeUpgradeable is Initializable, IERC1967Upgradeable { function __ERC1967Upgrade_init() internal onlyInitializing { } function __ERC1967Upgrade_init_unchained() internal onlyInitializing { } // 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 Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlotUpgradeable.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) { AddressUpgradeable.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 (StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT).value) { _setImplementation(newImplementation); } else { try IERC1822ProxiableUpgradeable(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 Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlotUpgradeable.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"); StorageSlotUpgradeable.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 Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract"); require( AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlotUpgradeable.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) { AddressUpgradeable.functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data); } } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. * * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. * * _Available since v4.1._ */ abstract contract UUPSUpgradeable is Initializable, IERC1822ProxiableUpgradeable, ERC1967UpgradeUpgradeable { function __UUPSUpgradeable_init() internal onlyInitializing { } function __UUPSUpgradeable_init_unchained() internal onlyInitializing { } /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment address private immutable __self = address(this); /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to * fail. */ modifier onlyProxy() { require(address(this) != __self, "Function must be called through delegatecall"); require(_getImplementation() == __self, "Function must be called through active proxy"); _; } /** * @dev Check that the execution is not being performed through a delegate call. This allows a function to be * callable on the implementing contract but not through proxies. */ modifier notDelegated() { require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall"); _; } /** * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the * implementation. It is used to validate the implementation's compatibility when performing an upgrade. * * 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. This is guaranteed by the `notDelegated` modifier. */ function proxiableUUID() external view virtual override notDelegated returns (bytes32) { return _IMPLEMENTATION_SLOT; } /** * @dev Upgrade the implementation of the proxy to `newImplementation`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. * * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function upgradeTo(address newImplementation) public virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); } /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. * * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data, true); } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeTo} and {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity * function _authorizeUpgrade(address) internal override onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract ContextUpgradeable is Initializable { function __Context_init() internal onlyInitializing { } function __Context_init_unchained() internal onlyInitializing { } function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; } /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ function __Ownable_init() internal onlyInitializing { __Ownable_init_unchained(); } function __Ownable_init_unchained() internal onlyInitializing { _transferOwnership(_msgSender()); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby disabling any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; } abstract contract SimplifiedModuleEvents { // module emit event when execution is successful on avator event ExecutionSuccess(); // module emit event when execution is failed on avator event ExecutionFailure(); } /** * @title Simplified Module Interface - A contract that can pass messages to a Module Manager contract if enabled by * that contract. * @dev Adapted from Zodiac's `Module.sol` at * https://github.com/gnosis/zodiac/tree/8a77e7b224af8004bd9f2ff4e2919642e93ffd85/contracts/core/Module.sol * , which * was audited https://github.com/gnosis/zodiac/tree/master/audits * This module removes target attribute, removes guard, and uses UUPS proxy. */ abstract contract SimplifiedModule is UUPSUpgradeable, OwnableUpgradeable, SimplifiedModuleEvents { /** * @dev Passes a transaction to be executed by the programmable account (i.e. "avatar" as per notion used in * Zodiac). * @notice Can only be called by this contract. * @param to Destination address of module transaction. * @param value Ether value of module transaction. * @param data Data payload of module transaction. * @param operation Operation type of module transaction: 0 == call, 1 == delegate call. */ function exec( address to, uint256 value, bytes memory data, Enum.Operation operation ) internal returns (bool success) { success = IAvatar(owner()).execTransactionFromModule(to, value, data, operation); if (success) { emit ExecutionSuccess(); } else { emit ExecutionFailure(); } } /** * @dev Passes a transaction to be executed by the avatar and returns data. * @notice Can only be called by this contract. * @param to Destination address of module transaction. * @param value Ether value of module transaction. * @param data Data payload of module transaction. * @param operation Operation type of module transaction: 0 == call, 1 == delegate call. */ function execAndReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) internal returns (bool success, bytes memory returnedData) { (success, returnedData) = IAvatar(owner()).execTransactionFromModuleReturnData(to, value, data, operation); if (success) { emit ExecutionSuccess(); } else { emit ExecutionFailure(); } } /** * @dev Override {_authorizeUpgrade} to only allow owner to upgrade the contract */ function _authorizeUpgrade(address) internal override(UUPSUpgradeable) onlyOwner { } } // OpenZeppelin Contracts (last updated v4.9.0) (utils/Multicall.sol) // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) /** * @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.8.0/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); } } } /** * @dev Provides a function to batch together multiple calls in a single external call. * * _Available since v4.1._ */ abstract contract Multicall { /** * @dev Receives and executes a batch of function calls on this contract. * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { results[i] = Address.functionDelegateCall(address(this), data[i]); } return results; } } // OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/IERC1820Registry.sol) /** * @dev Interface of the global ERC1820 Registry, as defined in the * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register * implementers for interfaces in this registry, as well as query support. * * Implementers may be shared by multiple accounts, and can also implement more * than a single interface for each account. Contracts can implement interfaces * for themselves, but externally-owned accounts (EOA) must delegate this to a * contract. * * {IERC165} interfaces can also be queried via the registry. * * For an in-depth explanation and source code analysis, see the EIP text. */ interface IERC1820Registry { event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); event ManagerChanged(address indexed account, address indexed newManager); /** * @dev Sets `newManager` as the manager for `account`. A manager of an * account is able to set interface implementers for it. * * By default, each account is its own manager. Passing a value of `0x0` in * `newManager` will reset the manager to this initial state. * * Emits a {ManagerChanged} event. * * Requirements: * * - the caller must be the current manager for `account`. */ function setManager(address account, address newManager) external; /** * @dev Returns the manager for `account`. * * See {setManager}. */ function getManager(address account) external view returns (address); /** * @dev Sets the `implementer` contract as ``account``'s implementer for * `interfaceHash`. * * `account` being the zero address is an alias for the caller's address. * The zero address can also be used in `implementer` to remove an old one. * * See {interfaceHash} to learn how these are created. * * Emits an {InterfaceImplementerSet} event. * * Requirements: * * - the caller must be the current manager for `account`. * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not * end in 28 zeroes). * - `implementer` must implement {IERC1820Implementer} and return true when * queried for support, unless `implementer` is the caller. See * {IERC1820Implementer-canImplementInterfaceForAddress}. */ function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external; /** * @dev Returns the implementer of `interfaceHash` for `account`. If no such * implementer is registered, returns the zero address. * * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 * zeroes), `account` will be queried for support of it. * * `account` being the zero address is an alias for the caller's address. */ function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address); /** * @dev Returns the interface hash for an `interfaceName`, as defined in the * corresponding * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. */ function interfaceHash(string calldata interfaceName) external pure returns (bytes32); /** * @notice Updates the cache with whether the contract implements an ERC165 interface or not. * @param account Address of the contract for which to update the cache. * @param interfaceId ERC165 interface for which to update the cache. */ function updateERC165Cache(address account, bytes4 interfaceId) external; /** * @notice Checks whether a contract implements an ERC165 interface or not. * If the result is not cached a direct lookup on the contract address is performed. * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling * {updateERC165Cache} with the contract address. * @param account Address of the contract to check. * @param interfaceId ERC165 interface to check. * @return True if `account` implements `interfaceId`, false otherwise. */ function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); /** * @notice Checks whether a contract implements an ERC165 interface or not without using or updating the cache. * @param account Address of the contract to check. * @param interfaceId ERC165 interface to check. * @return True if `account` implements `interfaceId`, false otherwise. */ function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); } // OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC1820Implementer.sol) // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC1820Implementer.sol) /** * @dev Interface for an ERC1820 implementer, as defined in the * https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface[EIP]. * Used by contracts that will be registered as implementers in the * {IERC1820Registry}. */ interface IERC1820Implementer { /** * @dev Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract * implements `interfaceHash` for `account`. * * See {IERC1820Registry-setInterfaceImplementer}. */ function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32); } /** * @dev Implementation of the {IERC1820Implementer} interface. * * Contracts may inherit from this and call {_registerInterfaceForAddress} to * declare their willingness to be implementers. * {IERC1820Registry-setInterfaceImplementer} should then be called for the * registration to be complete. * * CAUTION: This file is deprecated as of v4.9 and will be removed in the next major release. */ contract ERC1820Implementer is IERC1820Implementer { bytes32 private constant _ERC1820_ACCEPT_MAGIC = keccak256("ERC1820_ACCEPT_MAGIC"); mapping(bytes32 => mapping(address => bool)) private _supportedInterfaces; /** * @dev See {IERC1820Implementer-canImplementInterfaceForAddress}. */ function canImplementInterfaceForAddress( bytes32 interfaceHash, address account ) public view virtual override returns (bytes32) { return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00); } /** * @dev Declares the contract as willing to be an implementer of * `interfaceHash` for `account`. * * See {IERC1820Registry-setInterfaceImplementer} and * {IERC1820Registry-interfaceHash}. */ function _registerInterfaceForAddress(bytes32 interfaceHash, address account) internal virtual { _supportedInterfaces[interfaceHash][account] = true; } } // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); } // OpenZeppelin Contracts v4.4.1 (token/ERC777/IERC777Recipient.sol) /** * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. * * Accounts can be notified of {IERC777} tokens being sent to them by having a * contract implement this interface (contract holders can be their own * implementer) and registering it on the * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. * * See {IERC1820Registry} and {ERC1820Implementer}. */ interface IERC777Recipient { /** * @dev Called by an {IERC777} token contract whenever tokens are being * moved or created into a registered account (`to`). The type of operation * is conveyed by `from` being the zero address or not. * * This call occurs _after_ the token contract's state is updated, so * {IERC777-balanceOf}, etc., can be used to query the post-operation state. * * This function may revert to prevent the operation from being executed. */ function tokensReceived( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external; } // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol) // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) /** * @dev Standard math utilities missing in the Solidity language. */ library Math { enum Rounding { Down, // Toward negative infinity Up, // Toward infinity Zero // Toward zero } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds up instead * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } } // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } } /** * @dev String operations. */ library Strings { bytes16 private constant _SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toString(int256 value) internal pure returns (string memory) { return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value)))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, Math.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return keccak256(bytes(a)) == keccak256(bytes(b)); } } /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS, InvalidSignatureV // Deprecated in v4.8 } function _throwError(RecoverError error) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert("ECDSA: invalid signature"); } else if (error == RecoverError.InvalidSignatureLength) { revert("ECDSA: invalid signature length"); } else if (error == RecoverError.InvalidSignatureS) { revert("ECDSA: invalid signature 's' value"); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature` or error string. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, signature); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. * * _Available since v4.2._ */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. * * _Available since v4.3._ */ function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature); } return (signer, RecoverError.NoError); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; } /** * @dev Returns an Ethereum Signed Message, created from a `hash`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) { // 32 is the length in bytes of hash, // enforced by the type signature above /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") mstore(0x1c, hash) message := keccak256(0x00, 0x3c) } } /** * @dev Returns an Ethereum Signed Message, created from `s`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); } /** * @dev Returns an Ethereum Signed Typed Data, created from a * `domainSeparator` and a `structHash`. This produces hash corresponding * to the one signed with the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] * JSON-RPC method as part of EIP-712. * * See {recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, "\x19\x01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) data := keccak256(ptr, 0x42) } } /** * @dev Returns an Ethereum Signed Data with intended validator, created from a * `validator` and `data` according to the version 0 of EIP-191. * * See {recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19\x00", validator, data)); } } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * Bundles cryptographic primitives used by the HOPR protocol * */ abstract contract HoprCrypto { error InvalidFieldElement(); error InvalidCurvePoint(); error InvalidPointWitness(); // secp256k1: y^2 = x^3 + b (mod F_p) uint256 internal constant SECP256K1_B = 0x0000000000000000000000000000000000000000000000000000000000000007; // Field order created by secp256k1 curve // solhint-disable-next-line max-line-length uint256 internal constant SECP256K1_FIELD_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; // Order of the underlying field used for secp256k1 uint256 internal constant SECP256K1_BASE_FIELD_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; // x-component of base point of secp256k1 curve uint256 internal constant SECP256K1_BASE_POINT_X_COMPONENT = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798; // encoded sign of y-component of base point of secp256k1 curve uint8 internal constant SECP256K1_BASE_POINT_Y_COMPONENT_SIGN = 27; // E': y^2 = x^3 + A_PRIME + B_PRIME (mod F_p) // used by `hash_to_curve` function uint256 private constant A_PRIME = 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533; uint256 private constant B_PRIME = 1771; // Coefficients used for isogeneous mapping from E' to secp256k1 // see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#appx-iso-secp256k1 // // used by `hash_to_curve` function uint256 private constant K_10 = 0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7; uint256 private constant K_11 = 0x07d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581; uint256 private constant K_12 = 0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262; uint256 private constant K_13 = 0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c; uint256 private constant K_20 = 0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b; uint256 private constant K_21 = 0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14; uint256 private constant K_30 = 0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c; uint256 private constant K_31 = 0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3; uint256 private constant K_32 = 0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931; uint256 private constant K_33 = 0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84; uint256 private constant K_40 = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b; uint256 private constant K_41 = 0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573; uint256 private constant K_42 = 0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f; // Coefficients used for simplified SWU mapping // see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-suites-for-secp256k1 // // used by `hash_to_curve` function uint256 private constant Z = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC24; uint256 private constant C_1 = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0b; uint256 private constant C_2 = 0x31fdf302724013e57ad13fb38f842afeec184f00a74789dd286729c8303c4a59; // sqrt(-Z) uint256 private constant KECCAK256_BLOCKSIZE = 136; /** * Holds a compact ECDSA signature, following ERC-2098 */ struct CompactSignature { bytes32 r; bytes32 vs; } /** * Checks whether given value is an element of the secp256k1 field * * @param el element to check */ function isFieldElementInternal(uint256 el) internal pure returns (bool) { return 0 == el || el < SECP256K1_FIELD_ORDER; } /** * Checks whether given coordinates of P fulfill the secp256k1 curve equation * * @param pX first component of P * @param pY second component of P */ function isCurvePointInternal(uint256 pX, uint256 pY) internal pure returns (bool r) { // solhint-disable-next-line no-inline-assembly assembly { r := eq( mulmod(pY, pY, SECP256K1_BASE_FIELD_ORDER), addmod( SECP256K1_B, mulmod(mulmod(pX, pX, SECP256K1_BASE_FIELD_ORDER), pX, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER ) ) } } modifier isCurvePoint(uint256 pX, uint256 pY) { if (!isCurvePointInternal(pX, pY)) { revert InvalidCurvePoint(); } _; } modifier isFieldElement(uint256 el) { if (!isFieldElementInternal(el)) { revert InvalidFieldElement(); } _; } /** * Takes a `scalar` and returns the Ethereum address associated to * `scalar * G` where `G` is the base point of the secp256k1 curve. * * This function is necessary due to the missing ECMUL operation in Ethereum. It misuses the * ECRECOVER precompile to perform the scalar multiplication in a gas-efficient way * * For more information see * https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384 * * @param scalar to multiply with secp256k1 base point */ function scalarTimesBasepoint(uint256 scalar) internal pure returns (address) { return ecrecover( 0, SECP256K1_BASE_POINT_Y_COMPONENT_SIGN, bytes32(SECP256K1_BASE_POINT_X_COMPONENT), bytes32(mulmod(scalar, SECP256K1_BASE_POINT_X_COMPONENT, SECP256K1_FIELD_ORDER)) ); } /** * Takes a curve point `P = (pX, pY)` and a scalar and returns the Ethereum address associated * to the point `scalar * P` on the secp256k1 curve. * * This function is necessary due to the missing ECMUL operation in Ethereum. It misuses the * ECRECOVER precompile to perform the scalar multiplication in a gas-efficient way * * For more information see * https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384 * * @param scalar values to multiply P with * @param pX first component of P * @param pY second component of P */ function scalarPointMultiplication(uint256 scalar, uint256 pX, uint256 pY) internal pure returns (address) { uint8 sign; if (pY % 2 == 0) { sign = 27; } else { sign = 28; } return ecrecover(0, sign, bytes32(pX), bytes32(mulmod(scalar, pX, SECP256K1_FIELD_ORDER))); } /** * Converts a curve point P to an Ethereum address. * * This function can be used to witness the result of a scalar * multiplication. * * @param pX first component of P * @param pY second component of P */ function pointToAddress(uint256 pX, uint256 pY) internal pure returns (address) { return address(uint160(uint256(keccak256(abi.encodePacked(pX, pY))))); } /** * Adds two elliptic curve points P and Q using the general implementation. * * This function is optimized to perform one single point addition, e.g. * when using in a VRF or hash_to_curve scheme. * * @dev Throws if Q = -P since Infinity point is not supported. * * @dev This function is meant to be part of another function and thus does * not perform any sanity checks, such as if any of the given points * fulfill the curve equation. These checks are left to the caller of * the function. * * Optimizations: * - solidity assembly * - optimize for a single point addition * - inline modular inversion * * @param pX first component of P * @param pY second component of P * @param qX first component of Q * @param qY second component of Q * @param a curve parameter, y^2 = x^3 + a*x + b (mod p) */ function ecAdd( uint256 pX, uint256 pY, uint256 qX, uint256 qY, uint256 a ) internal view returns (uint256 rx, uint256 ry) { // solhint-disable-next-line no-inline-assembly assembly { if and(eq(pX, qX), not(eq(pY, qY))) { // Q = -P // which means P + Q = P - P = 0 which is not supported revert(0, 0) } let lambda let toInvert switch and(eq(pX, qX), eq(pY, qY)) // P == Q ? case true { // Point double toInvert := addmod(mulmod(2, pY, SECP256K1_BASE_FIELD_ORDER), a, SECP256K1_BASE_FIELD_ORDER) // 2 * p.y // compute (2 * p.y) ^ -1 using expmod precompile let payload := mload(0x40) mstore(payload, 0x20) // Length of Base mstore(add(payload, 0x20), 0x20) // Length of Exponent mstore(add(payload, 0x40), 0x20) // Length of Modulus mstore(add(payload, 0x60), toInvert) // Base mstore(add(payload, 0x80), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2D) // p - 1 mstore(add(payload, 0xa0), SECP256K1_BASE_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, payload, 0xC0, payload, 0x20)) { // 0x05 == expmod precompile revert(0, 0) } lambda := mulmod( // (3 * p.x ^ 2) * (2 * p.y) ^ -1 mulmod( // 3 * p.x ^ 2 3, mulmod(pX, pX, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER), mload(payload), SECP256K1_BASE_FIELD_ORDER ) } case false { // Point addition toInvert := addmod( // q.x - p.x qX, // q.x sub(SECP256K1_BASE_FIELD_ORDER, pX), // - p.x SECP256K1_BASE_FIELD_ORDER ) // compute (q.x - p.x) ^ -1 using expmod precompile let payload := mload(0x40) mstore(payload, 0x20) // Length of Base mstore(add(payload, 0x20), 0x20) // Length of Exponent mstore(add(payload, 0x40), 0x20) // Length of Modulus mstore(add(payload, 0x60), toInvert) // Base mstore(add(payload, 0x80), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2D) // p - 1 mstore(add(payload, 0xa0), SECP256K1_BASE_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, payload, 0xC0, payload, 0x20)) { // 0x05 == expmod precompile revert(0, 0) } lambda := mulmod( // (q.y - p.y) * (q.x - p.x) ^ -1 addmod( // q.y - p.y qY, // q.y sub(SECP256K1_BASE_FIELD_ORDER, pY), // - p.y SECP256K1_BASE_FIELD_ORDER ), mload(payload), // (q.x - p.x) ^ -1 SECP256K1_BASE_FIELD_ORDER ) } rx := addmod( // lambda^2 - q.x - p.x mulmod(lambda, lambda, SECP256K1_BASE_FIELD_ORDER), // lambda^2 addmod( // - q.x - p.x sub(SECP256K1_BASE_FIELD_ORDER, qX), // - q.x sub(SECP256K1_BASE_FIELD_ORDER, pX), // - p.x SECP256K1_BASE_FIELD_ORDER ), SECP256K1_BASE_FIELD_ORDER ) ry := addmod( // lambda * (p.x - r.x) - p.y mulmod( // lambda * (p.x - r.x) lambda, addmod( // p.x - r.x pX, // p.x sub(SECP256K1_BASE_FIELD_ORDER, rx), // - r.x SECP256K1_BASE_FIELD_ORDER ), SECP256K1_BASE_FIELD_ORDER ), sub(SECP256K1_BASE_FIELD_ORDER, pY), SECP256K1_BASE_FIELD_ORDER ) } } /** * Consumes a byte string and returns a pseudo-random secp256k1 curvepoint. * * Implements secp256k1_XMD:KECCAK_256_SSWU_RO_, see * https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html * * @dev DSTs longer than 255 bytes are considered unsound. * see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-domain-separation * * @param payload values "to hash" * @param dst domain separation tag, used to makes protocol instantiations unique */ function hashToCurve(bytes memory payload, bytes memory dst) internal view returns (uint256 rx, uint256 ry) { (uint256 u0, uint256 u1) = hash_to_field(payload, dst); (uint256 q0x, uint256 q0y) = mapToCurveSimpleSWU(uint256(u0)); // on isogenous curve (uint256 q1x, uint256 q1y) = mapToCurveSimpleSWU(uint256(u1)); // on isogenous curve // P + Q on isogenous curve (uint256 sx, uint256 sy) = ecAdd(q0x, q0y, q1x, q1y, A_PRIME); return mapPoint(sx, sy); } /** * Maps a curve point on E': y^2 = A'x^3 + B' to secp256k1. This function is necessary because * A*B = 0 for secp256k1 which is why the simplified SWU mapping is not directly applicable. * * A' := 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533 * B' := 1771 * modulus 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F (same as secp256k1) * * see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#appx-iso-secp256k1 * * Optimizations: * - mathematical optimization: reduce expmod / mulmod / addmod operations * - few temporary values to reduce memory expansion * - use Solidity assembly * * @param pX first component of P * @param pY second component of P */ function mapPoint(uint256 pX, uint256 pY) internal view returns (uint256 rx, uint256 ry) { // solhint-disable-next-line no-inline-assembly assembly { let pxSquare := mulmod(pX, pX, SECP256K1_BASE_FIELD_ORDER) // p.x * p.x let pxCubic := mulmod(pX, pxSquare, SECP256K1_BASE_FIELD_ORDER) // p.x * pxSquare // xNum = k_(1,3) * x'^3 + k_(1,2) * x'^2 + k_(1,1) * x' + k_(1,0) let xNum := addmod( addmod( mulmod(K_13, pxCubic, SECP256K1_BASE_FIELD_ORDER), mulmod(K_12, pxSquare, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER ), addmod(mulmod(K_11, pX, SECP256K1_BASE_FIELD_ORDER), K_10, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER ) // xDen = x'^2 + k_(2,1) * x' + k_(2,0) let xDen := addmod( addmod(pxSquare, mulmod(K_21, pX, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER), K_20, SECP256K1_BASE_FIELD_ORDER ) // computes xDen ^ -1 using expmod precompile let payload := mload(0x40) mstore(payload, 0x20) // Length of Base mstore(add(payload, 0x20), 0x20) // Length of Exponent mstore(add(payload, 0x40), 0x20) // Length of Modulus mstore(add(payload, 0x60), xDen) // Base mstore(add(payload, 0x80), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2D) // p - 1 mstore(add(payload, 0xa0), SECP256K1_BASE_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, payload, 0xC0, payload, 0x20)) { // 0x05 == expmod precompile revert(0, 0) } xDen := mload(payload) // x = xNum / xDen rx := mulmod(xNum, xDen, SECP256K1_BASE_FIELD_ORDER) // y_num = k_(3,3) * x'^3 + k_(3,2) * x'^2 + k_(3,1) * x' + k_(3,0) let y_num := addmod( addmod( mulmod(K_33, pxCubic, SECP256K1_BASE_FIELD_ORDER), mulmod(K_32, pxSquare, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER ), addmod(mulmod(K_31, pX, SECP256K1_BASE_FIELD_ORDER), K_30, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER ) // y_den = x'^3 + k_(4,2) * x'^2 + k_(4,1) * x' + k_(4,0) let y_den := addmod( addmod(pxCubic, mulmod(K_42, pxSquare, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER), addmod(mulmod(K_41, pX, SECP256K1_BASE_FIELD_ORDER), K_40, SECP256K1_BASE_FIELD_ORDER), SECP256K1_BASE_FIELD_ORDER ) // Computes (y_den ^ -1) using expmod precompile payload := mload(0x40) mstore(payload, 0x20) // Length of Base mstore(add(payload, 0x20), 0x20) // Length of Exponent mstore(add(payload, 0x40), 0x20) // Length of Modulus mstore(add(payload, 0x60), y_den) // Base mstore(add(payload, 0x80), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2D) // p - 1 mstore(add(payload, 0xa0), SECP256K1_BASE_FIELD_ORDER) // p if iszero(staticcall(not(0), 0x05, payload, 0xC0, payload, 0x20)) { // 0x05 == expmod precompile revert(0, 0) } y_den := mload(payload) // y = y' * y_num / y_den ry := mulmod(mulmod(pY, y_num, SECP256K1_BASE_FIELD_ORDER), y_den, SECP256K1_BASE_FIELD_ORDER) } } /** * Takes a field element and returns a curve point on an elliptic curve that is 3-isogenous * to secp256k1. * * Implements the simplified SWU mapping. Uses the optimized sample implementation from * https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method * * Optimizations: * - mathematical optimization: reduce expmod / mulmod / addmod operations * - few temporary values to reduce memory expansion * - Solidity assembly * * @param u the field element to map to a secp256k1 curve point */ function mapToCurveSimpleSWU(uint256 u) internal view returns (uint256 rx, uint256 ry) { // solhint-disable-next-line no-inline-assembly assembly { let tv1 := mulmod(u, u, SECP256K1_BASE_FIELD_ORDER) // 1. tv1 = u^2 tv1 := mulmod(Z, tv1, SECP256K1_BASE_FIELD_ORDER) // 2. tv1 = Z * tv1 let tv2 := mulmod(tv1, tv1, SECP256K1_BASE_FIELD_ORDER) // 3. tv2 = tv1^2 tv2 := addmod(tv2, tv1, SECP256K1_BASE_FIELD_ORDER) // 4. tv2 = tv2 + tv1 let tv3 := addmod(tv2, 1, SECP256K1_BASE_FIELD_ORDER) // 5. tv3 = tv2 + 1 tv3 := mulmod(tv3, B_PRIME, SECP256K1_BASE_FIELD_ORDER) // 6. tv3 = B * tv3 let tv4 switch eq(tv2, 0) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) case true { tv4 := Z } case false { tv4 := sub(SECP256K1_BASE_FIELD_ORDER, tv2) } tv4 := mulmod(A_PRIME, tv4, SECP256K1_BASE_FIELD_ORDER) // 8. tv4 = A * tv4 tv2 := mulmod(tv3, tv3, SECP256K1_BASE_FIELD_ORDER) // 9. tv2 = tv3^2 let tv6 := mulmod(tv4, tv4, SECP256K1_BASE_FIELD_ORDER) // 10. tv6 = tv4^2 let tv5 := mulmod(A_PRIME, tv6, SECP256K1_BASE_FIELD_ORDER) // 11. tv5 = A * tv6 tv2 := addmod(tv2, tv5, SECP256K1_BASE_FIELD_ORDER) // 12. tv2 = tv2 + tv5 tv2 := mulmod(tv2, tv3, SECP256K1_BASE_FIELD_ORDER) // 13. tv2 = tv2 * tv3 tv6 := mulmod(tv6, tv4, SECP256K1_BASE_FIELD_ORDER) // 14. tv6 = tv6 * tv4 tv5 := mulmod(B_PRIME, tv6, SECP256K1_BASE_FIELD_ORDER) // 15. tv5 = B * tv6 tv2 := addmod(tv2, tv5, SECP256K1_BASE_FIELD_ORDER) // 16. tv2 = tv2 + tv5 rx := mulmod(tv1, tv3, SECP256K1_BASE_FIELD_ORDER) // 17. x = tv1 * tv3 // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) let y1 let isSquare // sqrt_ratio_3mod4(u,v) subroutine // tv1 -> tv7 // tv2 -> tv8 // u -> tv2 // v -> tv6 // // Algorithm from: // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-optimized-sqrt_ratio-for-q- // =================================== { let tv7 := mulmod(tv6, tv6, SECP256K1_BASE_FIELD_ORDER) // 1. tv1 = v^2 let tv8 := mulmod(tv2, tv6, SECP256K1_BASE_FIELD_ORDER) // 2. tv2 = u * v tv7 := mulmod(tv7, tv8, SECP256K1_BASE_FIELD_ORDER) // 3. tv1 = tv1 * tv2 // 4. y1 = tv1^c1 (using expmod precompile) let p := mload(0x40) mstore(p, 0x20) // Length of Base mstore(add(p, 0x20), 0x20) // Length of Exponent mstore(add(p, 0x40), 0x20) // Length of Modulus mstore(add(p, 0x60), tv7) // Base mstore(add(p, 0x80), C_1) // Exponent mstore(add(p, 0xa0), SECP256K1_BASE_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, p, 0xC0, p, 0x20)) { // 0x05 == expmod precompile revert(0, 0) } let y1Inner := mulmod(mload(p), tv8, SECP256K1_BASE_FIELD_ORDER) // 5. y1 = y1 * tv2 let y2Inner := mulmod(y1Inner, C_2, SECP256K1_BASE_FIELD_ORDER) // 6. y2 = y1 * c2 let tv9 := mulmod(y1Inner, y1Inner, SECP256K1_BASE_FIELD_ORDER) // 7. tv3 = y1^2 tv9 := mulmod(tv9, tv6, SECP256K1_BASE_FIELD_ORDER) // 8. tv3 = tv3 * v switch eq(tv9, tv2) // 9. isQR = tv3 == u case true { // 10. y = CMOV(y2, y1, isQR) isSquare := true y1 := y1Inner } case false { isSquare := false y1 := y2Inner } } // ===================================== ry := mulmod(tv1, u, SECP256K1_BASE_FIELD_ORDER) // 19. y = tv1 * u ry := mulmod(ry, y1, SECP256K1_BASE_FIELD_ORDER) // 20. y = y * y1 if isSquare { rx := tv3 // 21. x = CMOV(x, tv3, is_gx1_square) ry := y1 // 22. y = CMOV(y, y1, is_gx1_square) } // 23. e1 = sgn0(u) == sgn0(y) if iszero(eq(mod(u, 2), mod(ry, 2))) { // sgn0(x) ~= x % 2 ry := sub(SECP256K1_BASE_FIELD_ORDER, ry) // 24. y = CMOV(-y, y, e1) } // compute tv4 ^ -1 let payload := mload(0x40) mstore(payload, 0x20) // Length of Base mstore(add(payload, 0x20), 0x20) // Length of Exponent mstore(add(payload, 0x40), 0x20) // Length of Modulus mstore(add(payload, 0x60), tv4) // Base mstore(add(payload, 0x80), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2D) // p-2 mstore(add(payload, 0xa0), SECP256K1_BASE_FIELD_ORDER) // p if iszero(staticcall(not(0), 0x05, payload, 0xC0, payload, 0x20)) { // 0x05 == expmod precompile revert(0, 0) } rx := mulmod(rx, mload(payload), SECP256K1_BASE_FIELD_ORDER) // 25. x = x / tv4 } } /** * Takes an arbitrary byte-string and a domain seperation tag (dst) and returns * two elements of the field used to create the secp256k1 curve. * * @dev DSTs longer than 255 bytes are considered unsound. * see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-domain-separation * * @param message the message to hash * @param dst domain separation tag, used to make protocol instantiations unique */ function hash_to_field(bytes memory message, bytes memory dst) internal view returns (uint256 u0, uint256 u1) { (bytes32 b1, bytes32 b2, bytes32 b3) = expand_message_xmd_keccak256(message, dst); // computes [...b1[..], ...b2[0..16]] ^ 1 mod n // solhint-disable-next-line no-inline-assembly assembly { let p := mload(0x40) // next free memory slot mstore(p, 0x30) // Length of Base mstore(add(p, 0x20), 0x20) // Length of Exponent mstore(add(p, 0x40), 0x20) // Length of Modulus mstore(add(p, 0x60), b1) // Base mstore(add(p, 0x80), b2) mstore(add(p, 0x90), 1) // Exponent mstore(add(p, 0xb0), SECP256K1_BASE_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, p, 0xD0, p, 0x20)) { revert(0, 0) } u0 := mload(p) } // computes [...b2[16..32], ...b3[..]] ^ 1 mod n // solhint-disable-next-line no-inline-assembly assembly { let p := mload(0x40) mstore(p, 0x30) // Length of Base mstore(add(p, 0x20), 0x20) // Length of Exponent mstore(add(p, 0x50), b2) mstore(add(p, 0x40), 0x20) // Length of Modulus mstore(add(p, 0x70), b3) // Base mstore(add(p, 0x90), 1) // Exponent mstore(add(p, 0xb0), SECP256K1_BASE_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, p, 0xD0, p, 0x20)) { revert(0, 0) } u1 := mload(p) } } /** * Takes an arbitrary bytestring and a domain seperation tag and returns a * pseudo-random scalar in the secp256k1 curve field. * * @dev DSTs longer than 255 bytes are considered unsound. * see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-domain-separation * * @param message the message to hash * @param dst domain separation tag, used to make protocol instantiations unique */ function hashToScalar(bytes memory message, bytes memory dst) internal view returns (uint256 u) { (bytes32 b1, bytes32 b2) = expand_message_xmd_keccak256_single(message, dst); // computes [...b1[0..32], ...b2[0..16]] ^ 1 mod n // solhint-disable-next-line no-inline-assembly assembly { let p := mload(0x40) // next free memory slot mstore(p, 0x30) // Length of Base mstore(add(p, 0x20), 0x20) // Length of Exponent mstore(add(p, 0x40), 0x20) // Length of Modulus mstore(add(p, 0x60), b1) // Base mstore(add(p, 0x80), b2) mstore(add(p, 0x90), 1) // Exponent mstore(add(p, 0xb0), SECP256K1_FIELD_ORDER) // Modulus if iszero(staticcall(not(0), 0x05, p, 0xD0, p, 0x20)) { revert(0, 0) } u := mload(p) } } /** * Expands an arbitrary byte-string to 96 bytes using the `expand_message_xmd` method described in * https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html * * Used for hash_to_curve functionality. * * @dev This is not a general implementation as the output length fixed. It is tailor-made * for secp256k1_XMD:KECCAK_256_SSWU_RO_ hash_to_curve implementation. * * @dev DSTs longer than 255 bytes are considered unsound. * see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-domain-separation * * @param message the message to hash * @param dst domain separation tag, used to make protocol instantiations unique */ function expand_message_xmd_keccak256( bytes memory message, bytes memory dst ) internal pure returns (bytes32 b1, bytes32 b2, bytes32 b3) { // solhint-disable-next-line no-inline-assembly assembly { if gt(mload(dst), 255) { revert(0, 0) } let b0 { // create payload for b0 hash let b0Payload := mload(0x40) // payload[0..KECCAK256_BLOCKSIZE] = 0 let b0PayloadO := KECCAK256_BLOCKSIZE // leave first block empty let msg_o := 0x20 // skip length prefix // payload[KECCAK256_BLOCKSIZE..KECCAK256_BLOCKSIZE+message.len()] = message[0..message.len()] for { let i := 0 } lt(i, mload(message)) { i := add(i, 0x20) } { mstore(add(b0Payload, b0PayloadO), mload(add(message, msg_o))) b0PayloadO := add(b0PayloadO, 0x20) msg_o := add(msg_o, 0x20) } // payload[KECCAK256_BLOCKSIZE+message.len()+1..KECCAK256_BLOCKSIZE+message.len()+2] = 96 b0PayloadO := add(mload(message), 137) mstore8(add(b0Payload, b0PayloadO), 0x60) // only support for 96 bytes output length let dstO := 0x20 b0PayloadO := add(b0PayloadO, 2) // payload[KECCAK256_BLOCKSIZE+message.len()+3..KECCAK256_BLOCKSIZE+message.len()+dst.len()] // = dst[0..dst.len()] for { let i := 0 } lt(i, mload(dst)) { i := add(i, 0x20) } { mstore(add(b0Payload, b0PayloadO), mload(add(dst, dstO))) b0PayloadO := add(b0PayloadO, 0x20) dstO := add(dstO, 0x20) } // payload[KECCAK256_BLOCKSIZE+message.len()+dst.len()..KECCAK256_BLOCKSIZE+message.len()+dst.len()+1] // = dst.len() b0PayloadO := add(add(mload(message), mload(dst)), 139) mstore8(add(b0Payload, b0PayloadO), mload(dst)) b0 := keccak256(b0Payload, add(140, add(mload(dst), mload(message)))) } // create payload for b1, b2 ... hashes let bIPayload := mload(0x40) mstore(bIPayload, b0) // payload[32..33] = 1 mstore8(add(bIPayload, 0x20), 1) let payloadO := 0x21 let dstO := 0x20 // payload[33..33+dst.len()] = dst[0..dst.len()] for { let i := 0 } lt(i, mload(dst)) { i := add(i, 0x20) } { mstore(add(bIPayload, payloadO), mload(add(dst, dstO))) payloadO := add(payloadO, 0x20) dstO := add(dstO, 0x20) } // payload[65+dst.len()..66+dst.len()] = dst.len() mstore8(add(bIPayload, add(0x21, mload(dst))), mload(dst)) b1 := keccak256(bIPayload, add(34, mload(dst))) // payload[0..32] = b0 XOR b1 mstore(bIPayload, xor(b0, b1)) // payload[32..33] = 2 mstore8(add(bIPayload, 0x20), 2) b2 := keccak256(bIPayload, add(34, mload(dst))) // payload[0..32] = b0 XOR b2 mstore(bIPayload, xor(b0, b2)) // payload[32..33] = 2 mstore8(add(bIPayload, 0x20), 3) b3 := keccak256(bIPayload, add(34, mload(dst))) } } /** * Expands an arbitrary byte-string to 48 bytes using the `expand_message_xmd` method described in * https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html * * Used for the VRF functionality. * * @dev This is not a general implementation as the output length fixed. It is tailor-made * for secp256k1_XMD:KECCAK_256_SSWU_RO_ hash_to_curve implementation. * * @dev DSTs longer than 255 bytes are considered unsound. * see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-domain-separation * * @param message the message to hash * @param dst domain separation tag, used to make protocol instantiations unique */ function expand_message_xmd_keccak256_single( bytes memory message, bytes memory dst ) internal pure returns (bytes32 b1, bytes32 b2) { // solhint-disable-next-line no-inline-assembly assembly { if gt(mload(dst), 255) { revert(0, 0) } let b0 { // create payload for b0 hash let b0Payload := mload(0x40) // payload[0..KECCAK256_BLOCKSIZE] = 0 let b0PayloadO := KECCAK256_BLOCKSIZE // leave first block empty let msg_o := 0x20 // skip length prefix // payload[KECCAK256_BLOCKSIZE..KECCAK256_BLOCKSIZE+message.len()] = message[0..message.len()] for { let i := 0 } lt(i, mload(message)) { i := add(i, 0x20) } { mstore(add(b0Payload, b0PayloadO), mload(add(message, msg_o))) b0PayloadO := add(b0PayloadO, 0x20) msg_o := add(msg_o, 0x20) } // payload[KECCAK256_BLOCKSIZE+message.len()+1..KECCAK256_BLOCKSIZE+message.len()+2] = 48 b0PayloadO := add(mload(message), 137) mstore8(add(b0Payload, b0PayloadO), 0x30) // only support for 48 bytes output length let dstO := 0x20 b0PayloadO := add(b0PayloadO, 2) // payload[KECCAK256_BLOCKSIZE+message.len()+3..KECCAK256_BLOCKSIZE+message.len()+dst.len()] // = dst[0..dst.len()] for { let i := 0 } lt(i, mload(dst)) { i := add(i, 0x20) } { mstore(add(b0Payload, b0PayloadO), mload(add(dst, dstO))) b0PayloadO := add(b0PayloadO, 0x20) dstO := add(dstO, 0x20) } // payload[KECCAK256_BLOCKSIZE+message.len()+dst.len()..KECCAK256_BLOCKSIZE+message.len()+dst.len()+1] // = dst.len() b0PayloadO := add(add(mload(message), mload(dst)), 139) mstore8(add(b0Payload, b0PayloadO), mload(dst)) b0 := keccak256(b0Payload, add(140, add(mload(dst), mload(message)))) } // create payload for b1, b2 ... hashes let bIPayload := mload(0x40) mstore(bIPayload, b0) // payload[32..33] = 1 mstore8(add(bIPayload, 0x20), 1) let payloadO := 0x21 let dstO := 0x20 // payload[33..33+dst.len()] = dst[0..dst.len()] for { let i := 0 } lt(i, mload(dst)) { i := add(i, 0x20) } { mstore(add(bIPayload, payloadO), mload(add(dst, dstO))) payloadO := add(payloadO, 0x20) dstO := add(dstO, 0x20) } // payload[65+dst.len()..66+dst.len()] = dst.len() mstore8(add(bIPayload, add(0x21, mload(dst))), mload(dst)) b1 := keccak256(bIPayload, add(34, mload(dst))) // payload[0..32] = b0 XOR b1 mstore(bIPayload, xor(b0, b1)) // payload[32..33] = 2 mstore8(add(bIPayload, 0x20), 2) b2 := keccak256(bIPayload, add(34, mload(dst))) } } /** * Bundles values to verify the validity of the VRF */ struct VRFParameters { // the main deterministic pseudo-random values uint256 vx; uint256 vy; // s = r + h * a, where r, a are kept hidden uint256 s; // hash over computed values uint256 h; // Ethereum only supports scalar multiplication to address // so we provide witnesses that are checked against // computed values uint256 sBx; // s * B uint256 sBy; uint256 hVx; // h * V uint256 hVy; } /** * Bundles payload used to create a VRF-generated deterministic * pseudo-random value. */ struct VRFPayload { // the main message, e.g. ticket Hash bytes32 message; // the "public key" of the signer, // necessary to make VRF individual for each Ethereum account address signer; // domain separation tag, make each protocol instantiation, // unique, such as staging and production environment, // must be at most 255 bytes, otherwise considered unsound // see https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-domain-separation bytes dst; } /** * Implements a VRF based on public-key cryptography using hash_to_curve primitive. * * Algorithm highly inspired by: * https://www.signal.org/docs/specifications/xeddsa/#vxeddsa * * @param params necessary values verify validity of VRF * @param payload values over which the VRF was computed, e.g. ticketHash */ function vrfVerify(VRFParameters memory params, VRFPayload memory payload) internal view returns (bool) { if (params.h >= SECP256K1_BASE_FIELD_ORDER || params.s >= SECP256K1_BASE_FIELD_ORDER) { revert InvalidFieldElement(); } if (!isCurvePointInternal(params.vx, params.vy)) { revert InvalidCurvePoint(); } // we get a pseudo-random curve point (uint256 bX, uint256 bY) = hashToCurve(abi.encodePacked(payload.signer, payload.message), payload.dst); // Mitigate missing ECMUL operation by using precomputed values and verify // against computed Ethereum address. address sBvMaybe = scalarPointMultiplication(params.s, bX, bY); if (sBvMaybe != pointToAddress(params.sBx, params.sBy)) { revert InvalidPointWitness(); } address hVMaybe = scalarPointMultiplication(params.h, params.vx, params.vy); if (hVMaybe != pointToAddress(params.hVx, params.hVy)) { revert InvalidPointWitness(); } // We checked the validity of precomputed sB and hV values, // now use them as if they were intermediate results. // R = sB - hV // solhint-disable-next-line max-line-length (uint256 rx, uint256 ry) = ecAdd(params.sBx, params.sBy, params.hVx, SECP256K1_BASE_FIELD_ORDER - params.hVy, 0); uint256 hCheck = hashToScalar(abi.encodePacked(payload.signer, params.vx, params.vy, rx, ry, payload.message), payload.dst); return hCheck == params.h; } } abstract contract HoprLedgerEvents { /** * Emitted once the ledger domain separator is updated. */ event LedgerDomainSeparatorUpdated(bytes32 indexed ledgerDomainSeparator); } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * Indexes data trustlessly to allow a fast-sync for nodes in the network. */ abstract contract HoprLedger is HoprLedgerEvents { string public constant LEDGER_VERSION = "1.0.0"; uint256 immutable snapshotInterval; /** * Stores the last indexer state * * Aligned to 1 EVM word */ struct RootStruct { bytes28 rootHash; // Overflow at year 2105 uint32 timestamp; } RootStruct latestRoot; RootStruct latestSnapshotRoot; bytes32 public ledgerDomainSeparator; /** * @param _snapshotInterval time in miliseconds to create a new snapshot */ constructor(uint256 _snapshotInterval) { snapshotInterval = _snapshotInterval; // take first 28 bytes latestRoot.rootHash = bytes28(keccak256(abi.encodePacked(address(this)))); latestRoot.timestamp = uint32(block.timestamp); latestSnapshotRoot = latestRoot; // compute the domain separator on deployment updateLedgerDomainSeparator(); } /** * @dev recompute the domain seperator in case of a fork * This function should be called by anyone when required. * An event is emitted when the domain separator is updated */ function updateLedgerDomainSeparator() public { // following encoding guidelines of EIP712 bytes32 newLedgerDomainSeparator = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("HoprLedger")), keccak256(bytes(LEDGER_VERSION)), block.chainid, address(this) ) ); if (newLedgerDomainSeparator != ledgerDomainSeparator) { ledgerDomainSeparator = newLedgerDomainSeparator; emit LedgerDomainSeparatorUpdated(ledgerDomainSeparator); } } function indexEvent(bytes memory payload) internal { bool createSnapshot = false; if (block.timestamp > latestRoot.timestamp + snapshotInterval) { createSnapshot = true; } // take first 28 bytes latestRoot.rootHash = bytes28( keccak256( // keep hashed data minimal abi.encodePacked( // ledger feed must be unique ledgerDomainSeparator, // Allows the verifier to detect up until which block the snapshot includes state changes uint32(block.number), // Bind result to previous root latestRoot.rootHash, // Information about the happened state change keccak256(payload) ) ) ); latestRoot.timestamp = uint32(block.timestamp); if (createSnapshot) { latestSnapshotRoot = latestRoot; } } } /** * @title HoprNodeManagementModule interface */ interface IHoprNodeManagementModule { function isHoprNodeManagementModule() external view returns (bool); function isNode(address nodeAddress) external view returns (bool); } abstract contract HoprNodeSafeRegistryEvents { /** * Emitted once a safe and node pair gets registered */ event RegisteredNodeSafe(address indexed safeAddress, address indexed nodeAddress); /** * Emitted once a safe and node pair gets deregistered */ event DergisteredNodeSafe(address indexed safeAddress, address indexed nodeAddress); /** * Emitted once the domain separator is updated. */ event DomainSeparatorUpdated(bytes32 indexed domainSeparator); } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * @title HoprNodeSafeRegistry * @dev Node safe must prove that the Safe is the only authorized controller of * the CHAIN_KEY address. This link between the Safe and node's chain-key address * should be registered upon successful verification * * The CHAIN_KEY address should not be a contract * The Safe addres should be a contract * This implies that Safe and CHAIN_KEY address cannot be the same. * * This contract is meant to be deployed as a standalone contract */ contract HoprNodeSafeRegistry is HoprNodeSafeRegistryEvents { using Address for address; // Node already has mapped to Safe error NodeHasSafe(); // Not a valid Safe address; error NotValidSafe(); // Not a valid signature from node; error NotValidSignatureFromNode(); // Safe address is zero error SafeAddressZero(); // Node address is zero error NodeAddressZero(); // Node address is a contract error NodeIsContract(); // Provided address is not a member of an enabled NodeManagementModule error NodeNotModuleMember(); // Structure to store the mapping between nodes and their associated Safe contracts struct NodeSafeRecord { address safeAddress; uint96 nodeSigNonce; } // Structure to represent a node-safe pair with a nonce struct NodeSafeNonce { address safeAddress; address nodeChainKeyAddress; uint256 nodeSigNonce; } // Currently deployed version, starting with 1.0.0 string public constant VERSION = "1.0.0"; bytes32 public domainSeparator; mapping(address => NodeSafeRecord) _nodeToSafe; // NodeSafeNonce struct type hash. // keccak256("NodeSafeNonce(address safeAddress,address nodeChainKeyAddress,uint256 nodeSigNonce)"); bytes32 public constant NODE_SAFE_TYPEHASH = hex"a8ac7aed128d1a2da0773fecc80b6265d15f7e62bf4401eb23bd46c3fcf5d2f8"; // start and end point for linked list of modules address private constant SENTINEL_MODULES = address(0x1); // page size of querying modules uint256 private constant pageSize = 100; /** * @dev Constructor function to initialize the contract state. * Computes the domain separator for EIP-712 verification. */ constructor() { // compute the domain separator on deployment updateDomainSeparator(); } /** * @dev Returns the Safe address associated with a specific node address. * @param nodeAddress The address of the Hopr node. * @return safeAddress The associated Safe address. */ function nodeToSafe(address nodeAddress) external view returns (address) { return _nodeToSafe[nodeAddress].safeAddress; } /** * @dev Returns the nonce of the signature for a specific node address. * @param nodeAddress The address of the Hopr node. * @return nodeSigNonce The nonce of the node's signature. */ function nodeSigNonce(address nodeAddress) external view returns (uint256) { return _nodeToSafe[nodeAddress].nodeSigNonce; } /** * @dev Checks whether a specific node-safe combination is registered. * @param safeAddress Address of safe * @param nodeChainKeyAddress Address of node * @return registered Whether the node-safe combination is registered. */ function isNodeSafeRegistered(address safeAddress, address nodeChainKeyAddress) external view returns (bool) { // If node is not registered to any safe, return false if (_nodeToSafe[nodeChainKeyAddress].safeAddress == address(0)) { return false; } return _nodeToSafe[nodeChainKeyAddress].safeAddress == safeAddress; } /** * @dev Register the Safe with a signature from the node. * This function can be called by any party. * @param safeAddress Address of safe * @param nodeChainKeyAddress Address of node * @param sig The signature provided by the node. */ function registerSafeWithNodeSig(address safeAddress, address nodeChainKeyAddress, bytes calldata sig) external { // check adminKeyAddress has added HOPR tokens to the staking contract. // Compute the hash of the struct according to EIP712 guidelines bytes32 hashStruct = keccak256( abi.encode( NODE_SAFE_TYPEHASH, safeAddress, nodeChainKeyAddress, _nodeToSafe[nodeChainKeyAddress].nodeSigNonce ) ); // Build the typed digest for signature verification bytes32 registerHash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, hashStruct)); // Verify that the signature is from nodeChainKeyAddress (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(registerHash, sig); if (error != ECDSA.RecoverError.NoError || recovered != nodeChainKeyAddress) { revert NotValidSignatureFromNode(); } // store those state, emit events etc. addNodeSafe(safeAddress, nodeChainKeyAddress); } /** * @dev Deregisters a Hopr node from its associated Safe and emits relevant events. * This function can only be called by the associated Safe. * @param nodeAddr The address of the Hopr node to be deregistered. */ function deregisterNodeBySafe(address nodeAddr) external { // check this node was registered to the caller if (_nodeToSafe[nodeAddr].safeAddress != msg.sender) { revert NotValidSafe(); } // Ensure that node is a member of the module ensureNodeIsSafeModuleMember(msg.sender, nodeAddr); // Update the state and emit the event _nodeToSafe[nodeAddr].safeAddress = address(0); emit DergisteredNodeSafe(msg.sender, nodeAddr); } /** * @dev Registers a Safe by the node through a direct function call. * This function is meant to be called by the Hopr node itself. * @param safeAddr The address of the Safe to be registered. */ function registerSafeByNode(address safeAddr) external { addNodeSafe(safeAddr, msg.sender); } /** * @dev Recomputes the domain separator in case of a network fork or update. * This function should be called by anyone when required. * An event is emitted when the domain separator is updated */ function updateDomainSeparator() public { // following encoding guidelines of EIP712 bytes32 newDomainSeparator = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("NodeSafeRegistry")), keccak256(bytes(VERSION)), block.chainid, address(this) ) ); if (newDomainSeparator != domainSeparator) { domainSeparator = newDomainSeparator; emit DomainSeparatorUpdated(domainSeparator); } } /** * @dev Internal function to store a node-safe pair and emit relevant events. * @param safeAddress Address of safe * @param nodeChainKeyAddress Address of node */ function addNodeSafe(address safeAddress, address nodeChainKeyAddress) internal { // Safe address cannot be zero if (safeAddress == address(0)) { revert SafeAddressZero(); } // Safe address cannot be zero if (nodeChainKeyAddress == address(0)) { revert NodeAddressZero(); } // Ensure that the node address is not a contract address if (nodeChainKeyAddress.isContract()) { revert NodeIsContract(); } // check this node hasn't been registered ower if (_nodeToSafe[nodeChainKeyAddress].safeAddress != address(0)) { revert NodeHasSafe(); } // ensure that node is a member of the (enabled) NodeManagementModule ensureNodeIsSafeModuleMember(safeAddress, nodeChainKeyAddress); NodeSafeRecord storage record = _nodeToSafe[nodeChainKeyAddress]; // update record record.safeAddress = safeAddress; record.nodeSigNonce = record.nodeSigNonce + 1; // as of Solidity 0.8, this reverts on overflows // update and emit event emit RegisteredNodeSafe(safeAddress, nodeChainKeyAddress); } /** * @dev Ensure that the node address is a member of * the enabled node management module of the safe * @param safeAddress Address of safe * @param nodeChainKeyAddress Address of node */ function ensureNodeIsSafeModuleMember(address safeAddress, address nodeChainKeyAddress) internal view { // nodeChainKeyAddress must be a member of the enabled node management module address nextModule; address[] memory modules; // there may be many modules, loop through them. Stop at the end point of the linked list while (nextModule != SENTINEL_MODULES) { // get modules for safe (modules, nextModule) = IAvatar(safeAddress).getModulesPaginated(SENTINEL_MODULES, pageSize); for (uint256 i = 0; i < modules.length; i++) { if ( IHoprNodeManagementModule(modules[i]).isHoprNodeManagementModule() && IHoprNodeManagementModule(modules[i]).isNode(nodeChainKeyAddress) ) { return; } } } // if nodeChainKeyAddress is not a member of a valid HoprNodeManagementModule to the safe, revert revert NodeNotModuleMember(); } } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * Provides modifiers to enforce usage of a MultiSig contract */ abstract contract HoprMultiSig { error AlreadyInitialized(); error MultiSigUninitialized(); error ContractNotResponsible(); error InvalidSafeAddress(); HoprNodeSafeRegistry registry; bool initialized = false; /** * Sets address of NodeSafeRegistry contract. * * @dev Must be called exactly once */ function setNodeSafeRegistry(HoprNodeSafeRegistry _registry) internal { if (initialized) { revert AlreadyInitialized(); } if (address(_registry) == address(0)) { revert InvalidSafeAddress(); } initialized = true; registry = _registry; } /** * Enforces usage of Safe contract specified in NodeSafeRegistry */ modifier onlySafe(address self) { if (!initialized) { revert MultiSigUninitialized(); } if (registry.nodeToSafe(self) != msg.sender) { revert ContractNotResponsible(); } _; } /** * Only permits operation if no Safe contract has been specified * in NodeSafeRegistry */ modifier noSafeSet() { if (!initialized) { revert MultiSigUninitialized(); } if (registry.nodeToSafe(msg.sender) != address(0)) { revert ContractNotResponsible(); } _; } } uint256 constant TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000; // in milliseconds uint256 constant INDEX_SNAPSHOT_INTERVAL = TWENTY_FOUR_HOURS; abstract contract HoprChannelsEvents { /** * Emitted once a channel is opened. * * Includes source and destination separately because mapping * (source, destination) -> channelId destroys information. */ event ChannelOpened(address indexed source, address indexed destination); /** * Emitted once balance of a channel is increased, e.g. after opening a * channel or redeeming a ticket. */ event ChannelBalanceIncreased(bytes32 indexed channelId, HoprChannels.Balance newBalance); /** * Emitted once balance of a channel is decreased, e.g. when redeeming * a ticket or closing a channel. */ event ChannelBalanceDecreased(bytes32 indexed channelId, HoprChannels.Balance newBalance); /** * Emitted once a party initiates the closure of an outgoing * channel. Includes the timestamp when the notice period is due. */ event OutgoingChannelClosureInitiated(bytes32 indexed channelId, HoprChannels.Timestamp closureTime); /** * Emitted once a channel closure is finalized. */ event ChannelClosed(bytes32 indexed channelId); /** * Emitted once a ticket is redeemed. Includes latest ticketIndex * since this value is necessary for issuing and validating tickets. */ event TicketRedeemed(bytes32 indexed channelId, HoprChannels.TicketIndex newTicketIndex); /** * Emitted once the domain separator is updated. */ event DomainSeparatorUpdated(bytes32 indexed domainSeparator); } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * Manages mixnet incentives in the hopr network. * */ contract HoprChannels is IERC777Recipient, ERC1820Implementer, Multicall, HoprLedger(INDEX_SNAPSHOT_INTERVAL), HoprMultiSig, HoprCrypto, HoprChannelsEvents { // required by ERC1820 spec IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); // required by ERC777 spec bytes32 public constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); type Balance is uint96; type TicketIndex is uint48; type TicketIndexOffset is uint32; type ChannelEpoch is uint24; type Timestamp is uint32; // overflows in year 2105 // Using IEEE 754 double precision -> 53 significant bits type WinProb is uint56; error InvalidBalance(); error BalanceExceedsGlobalPerChannelAllowance(); error SourceEqualsDestination(); error ZeroAddress(string reason); error TokenTransferFailed(); error InvalidNoticePeriod(); error NoticePeriodNotDue(); error WrongChannelState(string reason); error InvalidTicketSignature(); error InvalidVRFProof(); error InsufficientChannelBalance(); error TicketIsNotAWin(); error InvalidAggregatedTicketInterval(); error WrongToken(); error InvalidTokenRecipient(); error InvalidTokensReceivedUsage(); Balance public constant MAX_USED_BALANCE = Balance.wrap(10 ** 25); // 1% of total supply, staking more is not sound Balance public constant MIN_USED_BALANCE = Balance.wrap(1); // no empty token transactions // ERC-777 tokensReceived hook, fundChannelMulti uint256 public immutable ERC777_HOOK_FUND_CHANNEL_MULTI_SIZE = abi.encodePacked(address(0), Balance.wrap(0), address(0), Balance.wrap(0)).length; // ERC-777 tokensReceived hook, fundChannel uint256 public immutable ERC777_HOOK_FUND_CHANNEL_SIZE = abi.encodePacked(address(0), address(0)).length; string public constant VERSION = "2.0.0"; bytes32 public domainSeparator; // depends on chainId /** * @dev Channel state machine * redeemTicket() * ┌──────┐ * finalizeOutgoingChannelClosure() v │ * (after notice period), or ┌──────────────────────┐ * closeIncomingChannel() │ │ initiateOutgoingChannelClosure() * ┌────────────────│ Pending To Close │<─────────────────┐ * │ │ │ │ * │ └──────────────────────┘ │ * v │ * ┌────────────┐ tokensReceived() / fundChannel() ┌──────────┐ * │ │──────────────────────────────────────────────>│ │ * │ Closed │ closeIncomingChannel() │ Open │ * │ │<──────────────────────────────────────────────│ │ * └────────────┘ └──────────┘ * │ ^ * └──────┘ * redeemTicket() */ enum ChannelStatus { CLOSED, OPEN, PENDING_TO_CLOSE } /** * Represents the state of a channel * * Aligned to 2 EVM words */ struct Channel { // latest balance of the channel, changes whenever a ticket gets redeemed Balance balance; // prevents tickets from being replayed, increased with every redeemed ticket TicketIndex ticketIndex; // if set, timestamp once we can pull all funds from the channel Timestamp closureTime; // prevents tickets issued for older instantions to be replayed ChannelEpoch epoch; // current state of the channel ChannelStatus status; } /** * Represents a ticket that can be redeemed using `redeemTicket` function. * * Aligned to 2 EVM words */ struct TicketData { // ticket is valid in this channel bytes32 channelId; // amount of tokens to transfer if ticket is a win Balance amount; // highest channel.ticketIndex to accept when redeeming // ticket, used to aggregate tickets off-chain TicketIndex ticketIndex; // delta by which channel.ticketIndex gets increased when redeeming // the ticket, should be set to 1 if ticket is not aggregated, and >1 if // it is aggregated. Must never be <1. TicketIndexOffset indexOffset; // replay protection, invalidates all tickets once payment channel // gets closed ChannelEpoch epoch; // encoded winning probability of the ticket WinProb winProb; } /** * Bundles data that is necessary to redeem a ticket */ struct RedeemableTicket { // gives each ticket a unique identity and defines what this // ticket is worth TicketData data; // signature by the ticket issuer HoprCrypto.CompactSignature signature; // proof-of-relay secret computed by ticket redeemer, after // receiving keying material from next downstream node uint256 porSecret; } /** * Stores channels, indexed by their channelId */ mapping(bytes32 => Channel) public channels; /** * Token that will be used for all interactions. */ IERC20 public immutable token; /** * Notice period before fund from an outgoing channel can be pulled out. */ Timestamp public immutable noticePeriodChannelClosure; // in seconds /** * @param _token HoprToken address * @param _noticePeriodChannelClosure seconds until a channel can be closed * @param _safeRegistry address of the contract that maps from accounts to deployed Gnosis Safe instances */ constructor(address _token, Timestamp _noticePeriodChannelClosure, HoprNodeSafeRegistry _safeRegistry) { if (Timestamp.unwrap(_noticePeriodChannelClosure) == 0) { revert InvalidNoticePeriod(); } require(_token != address(0), "token must not be empty"); setNodeSafeRegistry(_safeRegistry); token = IERC20(_token); noticePeriodChannelClosure = _noticePeriodChannelClosure; _ERC1820_REGISTRY.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this)); updateDomainSeparator(); } /** * Assert that source and destination are good addresses, and distinct. */ modifier validateChannelParties(address source, address destination) { if (source == destination) { revert SourceEqualsDestination(); } if (source == address(0)) { revert ZeroAddress({ reason: "source must not be empty" }); } if (destination == address(0)) { revert ZeroAddress({ reason: "destination must not be empty" }); } _; } modifier validateBalance(Balance balance) { if (Balance.unwrap(balance) < Balance.unwrap(MIN_USED_BALANCE)) { revert InvalidBalance(); } if (Balance.unwrap(balance) > Balance.unwrap(MAX_USED_BALANCE)) { revert BalanceExceedsGlobalPerChannelAllowance(); } _; } /** * @dev recompute the domain seperator in case of a fork * This function should be called by anyone when required. * An event is emitted when the domain separator is updated */ function updateDomainSeparator() public { // following encoding guidelines of EIP712 bytes32 newDomainSeparator = keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("HoprChannels")), keccak256(bytes(VERSION)), block.chainid, address(this) ) ); if (newDomainSeparator != domainSeparator) { domainSeparator = newDomainSeparator; emit DomainSeparatorUpdated(domainSeparator); } } /** * See `_redeemTicketInternal`, entrypoint for MultiSig contract */ function redeemTicketSafe( address self, RedeemableTicket calldata redeemable, HoprCrypto.VRFParameters calldata params ) external HoprMultiSig.onlySafe(self) { _redeemTicketInternal(self, redeemable, params); } /** * See `_redeemTicketInternal` */ function redeemTicket( RedeemableTicket calldata redeemable, HoprCrypto.VRFParameters calldata params ) external HoprMultiSig.noSafeSet() { _redeemTicketInternal(msg.sender, redeemable, params); } /** * ~51k gas execution cost * Claims the incentive for relaying a mixnet packet using probabilistic payments. * * Verifies the outcome of a 3-to-4-party protocol: creator of the packet, ticket * issuer and ticket and next downstream node that acknowledges the reception and * the validity of the relayed mixnet packet. In many cases, the creator of the * packet and ticket redeemer is the same party. * * The packet creator states the challenge which gets fulfilled by presenting * `porSecret` (Proof-Of-Relay). The ticket issuer creates the ticket and signs * it. The provided signature acts as a source of entropy given by the ticket * issuer. The ticket redeemer ultimately receives a packet with a ticket next * to it. Once the ticket redeemer receives the acknowledgement from the next * downstream node, it can compute `porSecret`. * * When submitting the ticket, the ticket redeemer creates a deterministic * pseudo-random value that is verifiable by using its public key. This value is * unique for each ticket and adds entropy that can only be known by the ticket * redeemer. * * Tickets embed the incentive for relaying a single packet. To reduce on-chain * state changes, they can get aggregated before submitting to this function. * * Aggregated tickets define an validity interval such that the redemption of any * individual ticket invalidates the aggregated ticket and vice-versa. * * Used cryptographic primitives: * - ECDSA signature * - secp256k1 group homomorphism and DLP property * - hash_to_curve using simplified Shallue, van de Woestijne method * - Verifiable random function based on hash_to_curve * - pseudorandomness of keccak256 function * * @dev This method makes use of several methods to reduce stack height. * * @param self account address of the ticket redeemer * @param redeemable ticket, signature of ticket issuer, porSecret * @param params pseudo-random VRF value + proof that it was correctly using * ticket redeemer's private key */ function _redeemTicketInternal( address self, RedeemableTicket calldata redeemable, HoprCrypto.VRFParameters calldata params ) internal validateBalance(redeemable.data.amount) HoprCrypto.isFieldElement(redeemable.porSecret) { Channel storage spendingChannel = channels[redeemable.data.channelId]; if (spendingChannel.status != ChannelStatus.OPEN && spendingChannel.status != ChannelStatus.PENDING_TO_CLOSE) { revert WrongChannelState({ reason: "spending channel must be OPEN or PENDING_TO_CLOSE" }); } if (ChannelEpoch.unwrap(spendingChannel.epoch) != ChannelEpoch.unwrap(redeemable.data.epoch)) { revert WrongChannelState({ reason: "channel epoch must match" }); } // Aggregatable Tickets - validity interval: // A ticket has a base index and an offset. The offset must be > 0, // while the base index must be >= the currently set ticket index in the // channel. uint48 baseIndex = TicketIndex.unwrap(redeemable.data.ticketIndex); uint32 baseIndexOffset = TicketIndexOffset.unwrap(redeemable.data.indexOffset); uint48 currentIndex = TicketIndex.unwrap(spendingChannel.ticketIndex); if (baseIndexOffset < 1 || baseIndex < currentIndex) { revert InvalidAggregatedTicketInterval(); } if (Balance.unwrap(spendingChannel.balance) < Balance.unwrap(redeemable.data.amount)) { revert InsufficientChannelBalance(); } // Deviates from EIP712 due to computed property and non-standard struct property encoding bytes32 ticketHash = _getTicketHash(redeemable); if (!_isWinningTicket(ticketHash, redeemable, params)) { revert TicketIsNotAWin(); } HoprCrypto.VRFPayload memory payload = HoprCrypto.VRFPayload(ticketHash, self, abi.encodePacked(domainSeparator)); if (!vrfVerify(params, payload)) { revert InvalidVRFProof(); } address source = ECDSA.recover(ticketHash, redeemable.signature.r, redeemable.signature.vs); if (_getChannelId(source, self) != redeemable.data.channelId) { revert InvalidTicketSignature(); } spendingChannel.ticketIndex = TicketIndex.wrap(baseIndex + baseIndexOffset); spendingChannel.balance = Balance.wrap(Balance.unwrap(spendingChannel.balance) - Balance.unwrap(redeemable.data.amount)); indexEvent( abi.encodePacked(ChannelBalanceDecreased.selector, redeemable.data.channelId, spendingChannel.balance) ); emit ChannelBalanceDecreased(redeemable.data.channelId, spendingChannel.balance); bytes32 outgoingChannelId = _getChannelId(self, source); Channel storage earningChannel = channels[outgoingChannelId]; // Informs about new ticketIndex indexEvent(abi.encodePacked(TicketRedeemed.selector, redeemable.data.channelId, spendingChannel.ticketIndex)); emit TicketRedeemed(redeemable.data.channelId, spendingChannel.ticketIndex); if (earningChannel.status == ChannelStatus.CLOSED) { // The other channel does not exist, so we need to transfer funds directly if (token.transfer(msg.sender, Balance.unwrap(redeemable.data.amount)) != true) { revert TokenTransferFailed(); } } else { // this CAN produce channels with more stake than MAX_USED_AMOUNT - which does not lead // to overflows since total supply < type(uin96).max earningChannel.balance = Balance.wrap(Balance.unwrap(earningChannel.balance) + Balance.unwrap(redeemable.data.amount)); indexEvent(abi.encodePacked(ChannelBalanceIncreased.selector, outgoingChannelId, earningChannel.balance)); emit ChannelBalanceIncreased(outgoingChannelId, earningChannel.balance); } } /** * See `_initiateOutgoingChannelClosureInternal`, entrypoint for MultiSig contract */ function initiateOutgoingChannelClosureSafe( address self, address destination ) external HoprMultiSig.onlySafe(self) { _initiateOutgoingChannelClosureInternal(self, destination); } /** * See `_initiateOutgoingChannelClosureInternal` */ function initiateOutgoingChannelClosure(address destination) external HoprMultiSig.noSafeSet() { _initiateOutgoingChannelClosureInternal(msg.sender, destination); } /** * Prepares a channel to pull out funds from an outgoing channel. * * There is a notice period to give the other end, `destination`, the * opportunity to redeem their collected tickets. * * @param destination destination end of the channel to close */ function _initiateOutgoingChannelClosureInternal(address self, address destination) internal { // We can only initiate closure to outgoing channels bytes32 channelId = _getChannelId(self, destination); Channel storage channel = channels[channelId]; // calling initiateClosure on a PENDING_TO_CLOSE channel extends the noticePeriod if (channel.status == ChannelStatus.CLOSED) { revert WrongChannelState({ reason: "channel must have state OPEN or PENDING_TO_CLOSE" }); } channel.closureTime = Timestamp.wrap(Timestamp.unwrap(_currentBlockTimestamp()) + Timestamp.unwrap(noticePeriodChannelClosure)); channel.status = ChannelStatus.PENDING_TO_CLOSE; // Inform others at which time the notice period is due indexEvent(abi.encodePacked(OutgoingChannelClosureInitiated.selector, channelId, channel.closureTime)); emit OutgoingChannelClosureInitiated(channelId, channel.closureTime); } /** * See `_closeIncomingChannelInternal`, entrypoint for MultiSig contract */ function closeIncomingChannelSafe(address self, address source) external HoprMultiSig.onlySafe(self) { _closeIncomingChannelInternal(self, source); } /** * See `_closeIncomingChannelInternal` */ function closeIncomingChannel(address source) external HoprMultiSig.noSafeSet() { _closeIncomingChannelInternal(msg.sender, source); } /** * Closes an incoming channel. * * This can happen immediately since it is up to the caller to * redeem their collected tickets. * * @param source source end of the channel to close */ function _closeIncomingChannelInternal(address self, address source) internal { // We can only close incoming channels directly bytes32 channelId = _getChannelId(source, self); Channel storage channel = channels[channelId]; if (channel.status == ChannelStatus.CLOSED) { revert WrongChannelState({ reason: "channel must have state OPEN or PENDING_TO_CLOSE" }); } uint256 balance = Balance.unwrap(channel.balance); channel.status = ChannelStatus.CLOSED; // ChannelStatus.CLOSED == 0 channel.closureTime = Timestamp.wrap(0); channel.ticketIndex = TicketIndex.wrap(0); channel.balance = Balance.wrap(0); // channel.epoch must be kept indexEvent(abi.encodePacked(ChannelClosed.selector, channelId)); emit ChannelClosed(channelId); if (balance > 0) { if (token.transfer(source, balance) != true) { revert TokenTransferFailed(); } } } /** * See `_finalizeOutgoingChannelClosureInternal`, entrypoint for MultiSig contract */ function finalizeOutgoingChannelClosureSafe( address self, address destination ) external HoprMultiSig.onlySafe(self) { _finalizeOutgoingChannelClosureInternal(self, destination); } /** * See `_finalizeOutgoingChannelClosureInternal` */ function finalizeOutgoingChannelClosure(address destination) external HoprMultiSig.noSafeSet() { _finalizeOutgoingChannelClosureInternal(msg.sender, destination); } /** * Pulls out funds from an outgoing channel. Can be called once * notice period is due. * * @param destination the address of the counterparty */ function _finalizeOutgoingChannelClosureInternal(address self, address destination) internal { // We can only finalize closure to outgoing channels bytes32 channelId = _getChannelId(self, destination); Channel storage channel = channels[channelId]; if (channel.status != ChannelStatus.PENDING_TO_CLOSE) { revert WrongChannelState({ reason: "channel state must be PENDING_TO_CLOSE" }); } if (Timestamp.unwrap(channel.closureTime) >= Timestamp.unwrap(_currentBlockTimestamp())) { revert NoticePeriodNotDue(); } uint256 balance = Balance.unwrap(channel.balance); channel.status = ChannelStatus.CLOSED; // ChannelStatus.CLOSED == 0 channel.closureTime = Timestamp.wrap(0); channel.ticketIndex = TicketIndex.wrap(0); channel.balance = Balance.wrap(0); // channel.epoch must be kept indexEvent(abi.encodePacked(ChannelClosed.selector, channelId)); emit ChannelClosed(channelId); if (balance > 0) { if (token.transfer(msg.sender, balance) != true) { revert TokenTransferFailed(); } } } /** * ERC777.tokensReceived() hook, triggered by ERC777 token contract after * transfering tokens. * * Depending on the userData payload, this method either funds one * channel, or a bidirectional channel, consisting of two unidirectional * channels. * * Channel source and destination are specified by the userData payload. * * @dev This function reverts if it results in a no-op, i.e., no state change occurs. * @notice The opening of bidirectional channels is currently implemented for internal * and community testing purposes only, and is not intended for production use. * @param from The account from which the tokens have been transferred * @param to The account to which the tokens have been transferred * @param amount The amount of tokens transferred * @param userData The payload that determines the intended action */ function tokensReceived( address, // operator not needed address from, address to, uint256 amount, bytes calldata userData, bytes calldata // operatorData not needed ) external override { // don't accept any other tokens ;-) if (msg.sender != address(token)) { revert WrongToken(); } if (to != address(this)) { revert InvalidTokenRecipient(); } if (userData.length == 0) { // ERC777.tokensReceived() hook got called by `ERC777.send()` or // `ERC777.transferFrom()` which we can ignore at this point return; } // Opens an outgoing channel if (userData.length == ERC777_HOOK_FUND_CHANNEL_SIZE) { if (amount > type(uint96).max) { revert BalanceExceedsGlobalPerChannelAllowance(); } address src; address dest; assembly { src := shr(96, calldataload(userData.offset)) dest := shr(96, calldataload(add(userData.offset, 20))) } address safeAddress = registry.nodeToSafe(src); // skip the check between `from` and `src` on node-safe registry if (from == src) { // node if opening an outgoing channel if (safeAddress != address(0)) { revert ContractNotResponsible(); } } else { if (safeAddress != from) { revert ContractNotResponsible(); } } _fundChannelInternal(src, dest, Balance.wrap(uint96(amount))); // Opens two channels, donating msg.sender's tokens } else if (userData.length == ERC777_HOOK_FUND_CHANNEL_MULTI_SIZE) { address account1; Balance amount1; address account2; Balance amount2; assembly { account1 := shr(96, calldataload(userData.offset)) amount1 := shr(160, calldataload(add(0x14, userData.offset))) account2 := shr(96, calldataload(add(0x20, userData.offset))) amount2 := shr(160, calldataload(add(0x34, userData.offset))) } if (amount == 0 || amount != uint256(Balance.unwrap(amount1)) + uint256(Balance.unwrap(amount2))) { revert InvalidBalance(); } // fund channel in direction of: account1 -> account2 if (Balance.unwrap(amount1) > 0) { _fundChannelInternal(account1, account2, amount1); } // fund channel in direction of: account2 -> account1 if (Balance.unwrap(amount2) > 0) { _fundChannelInternal(account2, account1, amount2); } } else { revert InvalidTokensReceivedUsage(); } } /** * Fund an outgoing channel * Used in channel operation with Safe * * @param self address of the source * @param account address of the destination * @param amount amount to fund for channel */ function fundChannelSafe(address self, address account, Balance amount) external HoprMultiSig.onlySafe(self) { _fundChannelInternal(self, account, amount); // pull tokens from Safe and handle result if (token.transferFrom(msg.sender, address(this), Balance.unwrap(amount)) != true) { // sth. went wrong, we need to revert here revert TokenTransferFailed(); } } /** * Fund an outgoing channel by a node * @param account address of the destination * @param amount amount to fund for channel */ function fundChannel(address account, Balance amount) external HoprMultiSig.noSafeSet() { _fundChannelInternal(msg.sender, account, amount); // pull tokens from funder and handle result if (token.transferFrom(msg.sender, address(this), Balance.unwrap(amount)) != true) { // sth. went wrong, we need to revert here revert TokenTransferFailed(); } } /** * @dev Internal function to fund an outgoing channel from self to account with amount token * @notice only balance above zero can execute * * @param self source address * @param account destination address * @param amount token amount */ function _fundChannelInternal( address self, address account, Balance amount ) internal validateBalance(amount) validateChannelParties(self, account) { bytes32 channelId = _getChannelId(self, account); Channel storage channel = channels[channelId]; if (channel.status == ChannelStatus.PENDING_TO_CLOSE) { revert WrongChannelState({ reason: "cannot fund a channel that will close soon" }); } channel.balance = Balance.wrap(Balance.unwrap(channel.balance) + Balance.unwrap(amount)); if (channel.status == ChannelStatus.CLOSED) { // We are opening or reoping a channel channel.epoch = ChannelEpoch.wrap(ChannelEpoch.unwrap(channel.epoch) + 1); channel.ticketIndex = TicketIndex.wrap(0); channel.status = ChannelStatus.OPEN; indexEvent(abi.encodePacked(ChannelOpened.selector, self, account)); emit ChannelOpened(self, account); } indexEvent(abi.encodePacked(ChannelBalanceIncreased.selector, channelId, channel.balance)); emit ChannelBalanceIncreased(channelId, channel.balance); } // utility functions, no state changes involved /** * Computes the channel identifier * * @param source the address of source * @param destination the address of destination * @return the channel id */ function _getChannelId(address source, address destination) public pure returns (bytes32) { return keccak256(abi.encodePacked(source, destination)); } /** * Gets the current block timestamp correctly sliced to uint32 */ function _currentBlockTimestamp() public view returns (Timestamp) { // solhint-disable-next-line return Timestamp.wrap(uint32(block.timestamp)); } /** * Gets the hash of a ticket upon which the signature has been * created. Also used by the VRF. * * Tickets come with a signature from the ticket issuer and state a * challenge to be fulfilled when redeeming the ticket. As the validity * of the signature need to be checked before being able reconstruct * the response to the stated challenge, the signature includes the * challenge - rather the response, whereas the smart contract * requires the response. * * @param redeemable ticket data */ function _getTicketHash(RedeemableTicket calldata redeemable) public view returns (bytes32) { address challenge = HoprCrypto.scalarTimesBasepoint(redeemable.porSecret); // TicketData is aligned to exactly 2 EVM words, from which channelId // takes one. Removing channelId can thus be encoded in 1 EVM word. // // Tickets get signed and transferred in packed encoding, consuming // 148 bytes, including signature and challenge. Using tight encoding // for ticket hash unifies on-chain and off-chain usage of tickets. uint256 secondPart = (uint256(Balance.unwrap(redeemable.data.amount)) << 160) | (uint256(TicketIndex.unwrap(redeemable.data.ticketIndex)) << 112) | (uint256(TicketIndexOffset.unwrap(redeemable.data.indexOffset)) << 80) | (uint256(ChannelEpoch.unwrap(redeemable.data.epoch)) << 56) | uint256(WinProb.unwrap(redeemable.data.winProb)); // Deviates from EIP712 due to computed property and non-standard struct property encoding bytes32 hashStruct = keccak256( abi.encode( this.redeemTicket.selector, keccak256(abi.encodePacked(redeemable.data.channelId, secondPart, challenge)) ) ); return keccak256(abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, hashStruct)); } /** * Determines whether a ticket is considered a win. * * This is done by hashing values that must be revealed when redeeming tickets with * a property stated in the signed ticket. * * @param ticketHash hash of the ticket to check * @param redeemable ticket, opening, porSecret, signature * @param params VRF values, entropy given by ticket redeemer */ function _isWinningTicket( bytes32 ticketHash, RedeemableTicket calldata redeemable, HoprCrypto.VRFParameters calldata params ) public pure returns (bool) { // hash function produces 256 bits output but we require only first 56 bits (IEEE 754 double precision means 53 // signifcant bits) uint56 ticketProb = ( uint56( bytes7( keccak256( abi.encodePacked( // unique due to ticketIndex + ticketEpoch ticketHash, // use deterministic pseudo-random VRF output generated by redeemer params.vx, params.vy, // challenge-response packet sender + next downstream node redeemable.porSecret, // entropy by ticket issuer, only ticket issuer can generate a valid signature redeemable.signature.r, redeemable.signature.vs ) ) ) ) ); return ticketProb <= WinProb.unwrap(redeemable.data.winProb); } } // OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. // OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. enum Clearance { NONE, FUNCTION } enum TargetType { TOKEN, CHANNELS, SEND } enum TargetPermission { BLOCK_ALL, SPECIFIC_FALLBACK_BLOCK, SPECIFIC_FALLBACK_ALLOW, ALLOW_ALL } enum CapabilityPermission { NONE, BLOCK_ALL, SPECIFIC_FALLBACK_BLOCK, SPECIFIC_FALLBACK_ALLOW, ALLOW_ALL } /** * @dev it stores the following information in uint256 = (160 + 8 * 12) * (address) as uint160: targetAddress * (Clearance) as uint8: clearance * (TargetType) as uint8: targetType * (TargetPermission) as uint8: defaultTargetPermission (for the target) * (CapabilityPermission) as uint8: defaultRedeemTicketSafeFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: RESERVED FOR defaultBatchRedeemTicketsSafeFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: defaultCloseIncomingChannelSafeFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: defaultInitiateOutgoingChannelClosureSafeFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: defaultFinalizeOutgoingChannelClosureSafeFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: defaultFundChannelMultiFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: defaultSetCommitmentSafeFunctionPermisson (for Channels * contract) * (CapabilityPermission) as uint8: defaultApproveFunctionPermisson (for Token contract) * (CapabilityPermission) as uint8: defaultSendFunctionPermisson (for Token contract) */ type Target is uint256; /// capability permissions exceed maximum length error TooManyCapabilities(); /// cannot convert a function permission to target permission error PermissionNotFound(); library TargetUtils { uint256 internal constant NUM_CAPABILITY_PERMISSIONS = 9; function getNumCapabilityPermissions() internal pure returns (uint256) { return NUM_CAPABILITY_PERMISSIONS; } function getTargetAddress(Target target) internal pure returns (address) { return address(uint160(Target.unwrap(target) >> 96)); } function getTargetClearance(Target target) internal pure returns (Clearance) { // left shift 160 bits then right shift 256 - 8 bits return Clearance(uint8((Target.unwrap(target) << 160) >> 248)); } function getTargetType(Target target) internal pure returns (TargetType) { // left shift 160 + 8 bits then right shift 256 - 8 bits return TargetType(uint8((Target.unwrap(target) << 168) >> 248)); } function isTargetType(Target target, TargetType targetType) internal pure returns (bool) { // compare if the target type is expectec return getTargetType(target) == targetType; } function getDefaultTargetPermission(Target target) internal pure returns (TargetPermission) { // left shift 160 + 8 + 8 bits then right shift 256 - 8 bits return TargetPermission(uint8((Target.unwrap(target) << 176) >> 248)); } function getDefaultCapabilityPermissionAt( Target target, uint256 position ) internal pure returns (CapabilityPermission) { if (position >= NUM_CAPABILITY_PERMISSIONS) { revert TooManyCapabilities(); } // left shift 160 + 8 + 8 + 8 + 8 * pos bits then right shift 256 - 8 bits uint256 leftShiftBy = 184 + (8 * position); return CapabilityPermission(uint8((Target.unwrap(target) << leftShiftBy) >> 248)); } function forceWriteAsTargetType(Target target, TargetType targetType) internal pure returns (Target) { // remove value at TargetType position (22/32 bytes from left) // remove function permissions uint256 updatedTarget; uint256 typeMask; if (targetType == TargetType.CHANNELS) { /** * remove all the default token function permissions (uint16). Equivalent to * updatedTarget = (Target.unwrap(target) >> 16) << 16; * updatedTarget &= ~targetTypeMask; */ typeMask = uint256(bytes32(hex"ffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff0000")); } else if (targetType == TargetType.TOKEN) { /** * remove all the default function permissions (uint72) * add the last 16 bits (from right) back. Equivalent to * updatedTarget = (Target.unwrap(target) >> 72) << 72; * updatedTarget |= (Target.unwrap(target) << 240) >> 240; * updatedTarget &= ~targetTypeMask; */ typeMask = uint256(bytes32(hex"ffffffffffffffffffffffffffffffffffffffffff00ff00000000000000ffff")); } else { /** * remove all the default function permissions (uint72). Equivalent to * updatedTarget = (Target.unwrap(target) >> 72) << 72; * updatedTarget &= ~targetTypeMask; */ typeMask = uint256(bytes32(hex"ffffffffffffffffffffffffffffffffffffffffff00ff000000000000000000")); } updatedTarget = Target.unwrap(target) & typeMask; // force clear target type and overwrite with expected one updatedTarget |= uint256(targetType) << 80; return Target.wrap(updatedTarget); } function forceWriteTargetAddress(Target target, address targetAddress) internal pure returns (Target) { // remove the 160 bits from left uint256 updatedTarget = (Target.unwrap(target) << 160) >> 160; // add the target address to the left updatedTarget |= uint256(uint160(targetAddress)) << 96; return Target.wrap(updatedTarget); } /** * @dev Encode the target address, clearance, target type and default permissions to a Target type * @param targetAddress addres of the target * @param clearance clearance of the target * @param targetType Type of the target * @param targetPermission default target permissions * @param capabilityPermissions Array of default function permissions * Returns the wrapped target */ function encodeDefaultPermissions( address targetAddress, Clearance clearance, TargetType targetType, TargetPermission targetPermission, CapabilityPermission[] memory capabilityPermissions ) internal pure returns (Target target) { if (capabilityPermissions.length > NUM_CAPABILITY_PERMISSIONS) { revert TooManyCapabilities(); } uint256 _target; // include address to the first 160 bits _target |= uint256(uint160(targetAddress)) << 96; // include clearance to the next 8 bits (256 - 160 - 8 = 88) _target |= uint256(clearance) << 88; // inclue targetType to the next 8 bits (258 - 160 - 8 - 8 = 80) _target |= uint256(targetType) << 80; // inclue TargetPermission to the next 8 bits (258 - 160 - 8 - 8 - 8 = 72) _target |= uint256(targetPermission) << 72; // include the CapabilityPermissions to the last 8 * 9 = 72 bits for (uint256 i = 0; i < capabilityPermissions.length; i++) { // left shift 72 - 8 - 8 * i bits _target |= uint256(capabilityPermissions[i]) << (64 - (8 * i)); } return Target.wrap(_target); } /** * @dev Decode the target type to target address, clearance, target type and default permissions * @param target the wrapped target */ function decodeDefaultPermissions(Target target) internal pure returns ( address targetAddress, Clearance clearance, TargetType targetType, TargetPermission targetPermission, CapabilityPermission[] memory capabilityPermissions ) { // take the first 160 bits and parse it as address targetAddress = address(uint160(Target.unwrap(target) >> 96)); // take the next 8 bits as clearance clearance = Clearance(uint8((Target.unwrap(target) << 160) >> 248)); // take the next 8 bits as targetType targetType = TargetType(uint8((Target.unwrap(target) << 168) >> 248)); // decode default target permissions targetPermission = TargetPermission(uint8((Target.unwrap(target) << 176) >> 248)); // there are 1 default target permission and 8 default function permissions capabilityPermissions = new CapabilityPermission[](NUM_CAPABILITY_PERMISSIONS); // decode function permissions. By default, 8 function permissions for (uint256 i = 0; i < NUM_CAPABILITY_PERMISSIONS; i++) { // first left offset byt 184 + 8 * i bits // where 184 = 160 (address) + 8 (Clearance) + 8 (TargetType) + 8 (TargetPermission) // then RIGHT shift 256 - 8 = 248 bits capabilityPermissions[i] = CapabilityPermission(uint8((Target.unwrap(target) << (184 + (8 * i))) >> 248)); } } function convertFunctionToTargetPermission(CapabilityPermission capabilityPermission) internal pure returns (TargetPermission) { uint8 permissionIndex = uint8(capabilityPermission); if (permissionIndex == 0) { revert PermissionNotFound(); } return TargetPermission(permissionIndex - 1); } } struct TargetSet { // Storage of set values Target[] _values; // Position of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. // the key is not `Target` but the first 160 bits converted to address mapping(address => uint256) _indexes; } /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Adapted from OpenZeppelin's EnumerableSet and EnumerableMap (`AddressToUintMap`) * library, for TargetDefaultPermissions type. */ library EnumerableTargetSet { using TargetUtils for Target; // when the address is not stared as a target address error NonExistentKey(); /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(TargetSet storage set, Target value) internal returns (bool) { if (!contains(set, value.getTargetAddress())) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value.getTargetAddress()] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * @notice remove by the first 160 bits of target value (target address) instead of the target * Returns true if the value was removed from the set, that is if it was * present. */ function remove(TargetSet storage set, address targetAddress) internal returns (bool) { // We read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[targetAddress]; if (valueIndex != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; if (lastIndex != toDeleteIndex) { Target lastValue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastValue; // Update the index for the moved value set._indexes[lastValue.getTargetAddress()] = valueIndex; // Replace lastValue's index to valueIndex } // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[targetAddress]; return true; } else { return false; } } /** * @dev Returns true if the targetAddress (first 160 bits in `value`) is in the set. O(1). * @notice remove by the first 160 bits of target value (target address) instead of the target */ function contains(TargetSet storage set, address targetAddress) internal view returns (bool) { return set._indexes[targetAddress] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function length(TargetSet storage set) internal view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(TargetSet storage set, uint256 index) internal view returns (Target) { return set._values[index]; } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(TargetSet storage set) internal view returns (Target[] memory) { return set._values; } /** * @dev Tries to returns the value associated with the key `targetAddress`. O(1). * Does not revert if `targetAddress` is not in the map. */ function tryGet(TargetSet storage set, address targetAddress) internal view returns (bool, Target) { uint256 index = set._indexes[targetAddress]; if (index == 0) { return (false, Target.wrap(0)); } else { return (true, set._values[index - 1]); } } /** * @dev Returns the value associated with `targetAddress` key. O(1). * * Requirements: * * - `targetAddress` key must be in the map. */ function get(TargetSet storage set, address targetAddress) internal view returns (Target) { uint256 index = set._indexes[targetAddress]; if (index == 0) { revert NonExistentKey(); } return set._values[index - 1]; } } // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } } // OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol) // CAUTION // This version of SafeMath should only be used with Solidity 0.8 or later, // because it relies on the compiler's built in overflow checks. /** * @dev Wrappers over Solidity's arithmetic operations. * * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler * now has built in overflow checking. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the substraction of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return a - b; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { return a * b; } /** * @dev Returns the integer division of two unsigned integers, reverting on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return a / b; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return a % b; } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {trySub}. * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub( uint256 a, uint256 b, string memory errorMessage ) internal pure returns (uint256) { unchecked { require(b <= a, errorMessage); return a - b; } } /** * @dev Returns the integer division of two unsigned integers, reverting with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div( uint256 a, uint256 b, string memory errorMessage ) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a / b; } } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting with custom message when dividing by zero. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryMod}. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod( uint256 a, uint256 b, string memory errorMessage ) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a % b; } } } /** * @dev Interface of the ERC777Token standard as defined in the EIP. * * This contract uses the * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 registry standard] to let * token holders and recipients react to token movements by using setting implementers * for the associated interfaces in said registry. See {IERC1820Registry} and * {ERC1820Implementer}. */ interface IERC777 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() external view returns (string memory); /** * @dev Returns the smallest part of the token that is not divisible. This * means all token operations (creation, movement and destruction) must have * amounts that are a multiple of this number. * * For most token contracts, this value will equal 1. */ function granularity() external view returns (uint256); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by an account (`owner`). */ function balanceOf(address owner) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * If send or receive hooks are registered for the caller and `recipient`, * the corresponding functions will be called with `data` and empty * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. * * Emits a {Sent} event. * * Requirements * * - the caller must have at least `amount` tokens. * - `recipient` cannot be the zero address. * - if `recipient` is a contract, it must implement the {IERC777Recipient} * interface. */ function send( address recipient, uint256 amount, bytes calldata data ) external; /** * @dev Destroys `amount` tokens from the caller's account, reducing the * total supply. * * If a send hook is registered for the caller, the corresponding function * will be called with `data` and empty `operatorData`. See {IERC777Sender}. * * Emits a {Burned} event. * * Requirements * * - the caller must have at least `amount` tokens. */ function burn(uint256 amount, bytes calldata data) external; /** * @dev Returns true if an account is an operator of `tokenHolder`. * Operators can send and burn tokens on behalf of their owners. All * accounts are their own operator. * * See {operatorSend} and {operatorBurn}. */ function isOperatorFor(address operator, address tokenHolder) external view returns (bool); /** * @dev Make an account an operator of the caller. * * See {isOperatorFor}. * * Emits an {AuthorizedOperator} event. * * Requirements * * - `operator` cannot be calling address. */ function authorizeOperator(address operator) external; /** * @dev Revoke an account's operator status for the caller. * * See {isOperatorFor} and {defaultOperators}. * * Emits a {RevokedOperator} event. * * Requirements * * - `operator` cannot be calling address. */ function revokeOperator(address operator) external; /** * @dev Returns the list of default operators. These accounts are operators * for all token holders, even if {authorizeOperator} was never called on * them. * * This list is immutable, but individual holders may revoke these via * {revokeOperator}, in which case {isOperatorFor} will return false. */ function defaultOperators() external view returns (address[] memory); /** * @dev Moves `amount` tokens from `sender` to `recipient`. The caller must * be an operator of `sender`. * * If send or receive hooks are registered for `sender` and `recipient`, * the corresponding functions will be called with `data` and * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. * * Emits a {Sent} event. * * Requirements * * - `sender` cannot be the zero address. * - `sender` must have at least `amount` tokens. * - the caller must be an operator for `sender`. * - `recipient` cannot be the zero address. * - if `recipient` is a contract, it must implement the {IERC777Recipient} * interface. */ function operatorSend( address sender, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData ) external; /** * @dev Destroys `amount` tokens from `account`, reducing the total supply. * The caller must be an operator of `account`. * * If a send hook is registered for `account`, the corresponding function * will be called with `data` and `operatorData`. See {IERC777Sender}. * * Emits a {Burned} event. * * Requirements * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. * - the caller must be an operator for `account`. */ function operatorBurn( address account, uint256 amount, bytes calldata data, bytes calldata operatorData ) external; event Sent( address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData ); event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); event AuthorizedOperator(address indexed operator, address indexed tokenHolder); event RevokedOperator(address indexed operator, address indexed tokenHolder); } /** * @dev Interface of the ERC777TokensSender standard as defined in the EIP. * * {IERC777} Token holders can be notified of operations performed on their * tokens by having a contract implement this interface (contract holders can be * their own implementer) and registering it on the * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. * * See {IERC1820Registry} and {ERC1820Implementer}. */ interface IERC777Sender { /** * @dev Called by an {IERC777} token contract whenever a registered holder's * (`from`) tokens are about to be moved or destroyed. The type of operation * is conveyed by `to` being the zero address or not. * * This call occurs _before_ the token contract's state is updated, so * {IERC777-balanceOf}, etc., can be used to query the pre-operation state. * * This function may revert to prevent the operation from being executed. */ function tokensToSend( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external; } /** * @dev Implementation of the {IERC777} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * * Support for ERC20 is included in this contract, as specified by the EIP: both * the ERC777 and ERC20 interfaces can be safely used when interacting with it. * Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token * movements. * * Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there * are no special restrictions in the amount of tokens that created, moved, or * destroyed. This makes integration with ERC20 applications seamless. */ contract ERC777 is Context, IERC777, IERC20 { using SafeMath for uint256; using Address for address; IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); mapping(address => uint256) private _balances; uint256 private _totalSupply; string private _name; string private _symbol; // We inline the result of the following hashes because Solidity doesn't resolve them at compile time. // See https://github.com/ethereum/solidity/issues/4024. // keccak256("ERC777TokensSender") bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = 0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895; // keccak256("ERC777TokensRecipient") bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; // This isn't ever read from - it's only used to respond to the defaultOperators query. address[] private _defaultOperatorsArray; // Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). mapping(address => bool) private _defaultOperators; // For each account, a mapping of its operators and revoked default operators. mapping(address => mapping(address => bool)) private _operators; mapping(address => mapping(address => bool)) private _revokedDefaultOperators; // ERC20-allowances mapping(address => mapping(address => uint256)) private _allowances; /** * @dev `defaultOperators` may be an empty array. */ constructor( string memory name_, string memory symbol_, address[] memory defaultOperators_ ) { _name = name_; _symbol = symbol_; _defaultOperatorsArray = defaultOperators_; for (uint256 i = 0; i < _defaultOperatorsArray.length; i++) { _defaultOperators[_defaultOperatorsArray[i]] = true; } // register interfaces _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256('ERC777Token'), address(this)); _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256('ERC20Token'), address(this)); } /** * @dev See {IERC777-name}. */ function name() public view override returns (string memory) { return _name; } /** * @dev See {IERC777-symbol}. */ function symbol() public view override returns (string memory) { return _symbol; } /** * @dev See {ERC20-decimals}. * * Always returns 18, as per the * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). */ function decimals() public pure returns (uint8) { return 18; } /** * @dev See {IERC777-granularity}. * * This implementation always returns `1`. */ function granularity() public view override returns (uint256) { return 1; } /** * @dev See {IERC777-totalSupply}. */ function totalSupply() public view override(IERC20, IERC777) returns (uint256) { return _totalSupply; } /** * @dev Returns the amount of tokens owned by an account (`tokenHolder`). */ function balanceOf(address tokenHolder) public view override(IERC20, IERC777) returns (uint256) { return _balances[tokenHolder]; } /** * @dev See {IERC777-send}. * * Also emits a {IERC20-Transfer} event for ERC20 compatibility. */ function send( address recipient, uint256 amount, bytes memory data ) public virtual override { _send(_msgSender(), recipient, amount, data, '', true); } /** * @dev See {IERC20-transfer}. * * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient} * interface if it is a contract. * * Also emits a {Sent} event. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { require(recipient != address(0), 'ERC777: transfer to the zero address'); address from = _msgSender(); _callTokensToSend(from, from, recipient, amount, '', ''); _move(from, from, recipient, amount, '', ''); _callTokensReceived(from, from, recipient, amount, '', '', false); return true; } /** * @dev See {IERC777-burn}. * * Also emits a {IERC20-Transfer} event for ERC20 compatibility. */ function burn(uint256 amount, bytes memory data) public virtual override { _burn(_msgSender(), amount, data, ''); } /** * @dev See {IERC777-isOperatorFor}. */ function isOperatorFor(address operator, address tokenHolder) public view override returns (bool) { return operator == tokenHolder || (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || _operators[tokenHolder][operator]; } /** * @dev See {IERC777-authorizeOperator}. */ function authorizeOperator(address operator) public virtual override { require(_msgSender() != operator, 'ERC777: authorizing self as operator'); if (_defaultOperators[operator]) { delete _revokedDefaultOperators[_msgSender()][operator]; } else { _operators[_msgSender()][operator] = true; } emit AuthorizedOperator(operator, _msgSender()); } /** * @dev See {IERC777-revokeOperator}. */ function revokeOperator(address operator) public virtual override { require(operator != _msgSender(), 'ERC777: revoking self as operator'); if (_defaultOperators[operator]) { _revokedDefaultOperators[_msgSender()][operator] = true; } else { delete _operators[_msgSender()][operator]; } emit RevokedOperator(operator, _msgSender()); } /** * @dev See {IERC777-defaultOperators}. */ function defaultOperators() public view override returns (address[] memory) { return _defaultOperatorsArray; } /** * @dev See {IERC777-operatorSend}. * * Emits {Sent} and {IERC20-Transfer} events. */ function operatorSend( address sender, address recipient, uint256 amount, bytes memory data, bytes memory operatorData ) public virtual override { require(isOperatorFor(_msgSender(), sender), 'ERC777: caller is not an operator for holder'); _send(sender, recipient, amount, data, operatorData, true); } /** * @dev See {IERC777-operatorBurn}. * * Emits {Burned} and {IERC20-Transfer} events. */ function operatorBurn( address account, uint256 amount, bytes memory data, bytes memory operatorData ) public virtual override { require(isOperatorFor(_msgSender(), account), 'ERC777: caller is not an operator for holder'); _burn(account, amount, data, operatorData); } /** * @dev See {IERC20-allowance}. * * Note that operator and allowance concepts are orthogonal: operators may * not have allowance, and accounts with allowance may not be operators * themselves. */ function allowance(address holder, address spender) public view override returns (uint256) { return _allowances[holder][spender]; } /** * @dev See {IERC20-approve}. * * Note that accounts cannot have allowance issued by their operators. */ function approve(address spender, uint256 value) public virtual override returns (bool) { address holder = _msgSender(); _approve(holder, spender, value); return true; } /** * @dev See {IERC20-transferFrom}. * * Note that operator and allowance concepts are orthogonal: operators cannot * call `transferFrom` (unless they have allowance), and accounts with * allowance cannot call `operatorSend` (unless they are operators). * * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. */ function transferFrom( address holder, address recipient, uint256 amount ) public virtual override returns (bool) { require(recipient != address(0), 'ERC777: transfer to the zero address'); require(holder != address(0), 'ERC777: transfer from the zero address'); address spender = _msgSender(); _callTokensToSend(spender, holder, recipient, amount, '', ''); _move(spender, holder, recipient, amount, '', ''); _approve(holder, spender, _allowances[holder][spender].sub(amount, 'ERC777: transfer amount exceeds allowance')); _callTokensReceived(spender, holder, recipient, amount, '', '', false); return true; } /** * @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * If a send hook is registered for `account`, the corresponding function * will be called with `operator`, `data` and `operatorData`. * * See {IERC777Sender} and {IERC777Recipient}. * * Emits {Minted} and {IERC20-Transfer} events. * * Requirements * * - `account` cannot be the zero address. * - if `account` is a contract, it must implement the {IERC777Recipient} * interface. */ function _mint( address account, uint256 amount, bytes memory userData, bytes memory operatorData ) internal virtual { require(account != address(0), 'ERC777: mint to the zero address'); address operator = _msgSender(); _beforeTokenTransfer(operator, address(0), account, amount); // Update state variables _totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount); _callTokensReceived(operator, address(0), account, amount, userData, operatorData, true); emit Minted(operator, account, amount, userData, operatorData); emit Transfer(address(0), account, amount); } /** * @dev Send tokens * @param from address token holder address * @param to address recipient address * @param amount uint256 amount of tokens to transfer * @param userData bytes extra information provided by the token holder (if any) * @param operatorData bytes extra information provided by the operator (if any) * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient */ function _send( address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck ) internal virtual { require(from != address(0), 'ERC777: send from the zero address'); require(to != address(0), 'ERC777: send to the zero address'); address operator = _msgSender(); _callTokensToSend(operator, from, to, amount, userData, operatorData); _move(operator, from, to, amount, userData, operatorData); _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck); } /** * @dev Burn tokens * @param from address token holder address * @param amount uint256 amount of tokens to burn * @param data bytes extra information provided by the token holder * @param operatorData bytes extra information provided by the operator (if any) */ function _burn( address from, uint256 amount, bytes memory data, bytes memory operatorData ) internal virtual { require(from != address(0), 'ERC777: burn from the zero address'); address operator = _msgSender(); _callTokensToSend(operator, from, address(0), amount, data, operatorData); _beforeTokenTransfer(operator, from, address(0), amount); // Update state variables _balances[from] = _balances[from].sub(amount, 'ERC777: burn amount exceeds balance'); _totalSupply = _totalSupply.sub(amount); emit Burned(operator, from, amount, data, operatorData); emit Transfer(from, address(0), amount); } function _move( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData ) private { _beforeTokenTransfer(operator, from, to, amount); _balances[from] = _balances[from].sub(amount, 'ERC777: transfer amount exceeds balance'); _balances[to] = _balances[to].add(amount); emit Sent(operator, from, to, amount, userData, operatorData); emit Transfer(from, to, amount); } /** * @dev See {ERC20-_approve}. * * Note that accounts cannot have allowance issued by their operators. */ function _approve( address holder, address spender, uint256 value ) internal { require(holder != address(0), 'ERC777: approve from the zero address'); require(spender != address(0), 'ERC777: approve to the zero address'); _allowances[holder][spender] = value; emit Approval(holder, spender, value); } /** * @dev Call from.tokensToSend() if the interface is registered * @param operator address operator requesting the transfer * @param from address token holder address * @param to address recipient address * @param amount uint256 amount of tokens to transfer * @param userData bytes extra information provided by the token holder (if any) * @param operatorData bytes extra information provided by the operator (if any) */ function _callTokensToSend( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData ) private { address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH); if (implementer != address(0)) { IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); } } /** * @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but * tokensReceived() was not registered for the recipient * @param operator address operator requesting the transfer * @param from address token holder address * @param to address recipient address * @param amount uint256 amount of tokens to transfer * @param userData bytes extra information provided by the token holder (if any) * @param operatorData bytes extra information provided by the operator (if any) * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient */ function _callTokensReceived( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck ) private { address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); if (implementer != address(0)) { IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); } else if (requireReceptionAck) { require(!to.isContract(), 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient'); } } /** * @dev Hook that is called before any token transfer. This includes * calls to {send}, {transfer}, {operatorSend}, minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be to transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address operator, address from, address to, uint256 amount ) internal virtual {} } enum GranularPermission { NONE, ALLOW, BLOCK } struct Role { TargetSet targets; // target addresses that can be called mapping(address => bool) members; // eligible caller. May be able to receive native tokens (e.g. xDAI), if set to // allowed // For CHANNELS target: capabilityKey (bytes32) => channel Id (keccak256(src, dest)) => GranularPermission // For TOKEN target: capabilityKey (bytes32) => pair Id (keccak256(node address, spender address)) => // GranularPermission // For SEND target: bytes32(0x00) => pair Id (keccak256(node address, spender address)) => GranularPermission mapping(bytes32 => mapping(bytes32 => GranularPermission)) capabilities; } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * @dev Drawing inspiration from the `zodiac-modifier-roles-v1` `Permissions.sol` contract, * this library is designed to support a single role and offers a set of specific functions * for interacting with HoprChannels and HoprToken contracts * * Adapted from `Permissions.sol` at commit 454be9d3c26f90221ca717518df002d1eca1845f, which * was audited https://github.com/gnosis/zodiac-modifier-roles-v1/tree/main/packages/evm/docs * * It is specifically tailored for interaction with HoprChannels and HoprToken contracts. * Additionally, it enables the transfer of native tokens to designated addresses, * while restricting the invocation of payable functions. * * Some difference between this library and the original `Permissions.sol` contract are: * - This library is designed to support a single role * - No `DelegateCall` is allowed * - Target must be one of the three types: Token, Channels, SEND * - Only scoped functions are allowed. No more wildcard * - Calling payable function is not allowed. * - When calling HoprChannels contracts, permission is check with multiple parameters together * - For Channels targets, the default permission is ALLOWED. However, the default value for other targets is BLOCKED. * - Permissions are not stored bitwise in `scopeConig` (uint256) due to lack of customization * - Utility functions, such as `packLeft`, `packRight`, `unpackFunction`, `unpackParameter`, `checkExecutionOptions` * are removed * - Specific helper functions, such as `pluckOneStaticAddress`, `pluckTwoStaticAddresses`, `pluckDynamicAddresses`, `pluckSendPayload` * are derived from `pluckStaticValue` and `pluckDynamicValue` * - helper functions to encode array of function signatures and their respective permissions are added. * * @notice Due to the deployed HoprToken.sol imports OpenZeppelin contract library locked at v4.4.2, while * HoprChannels contract imports OpenZeppelin contract of v4.8.3, it's not possible to import both contracts * the same time without creating conflicts. Therefore, two method identifiers of HoprToken contract are * defined with value instead of `.selector` */ library HoprCapabilityPermissions { using TargetUtils for Target; using EnumerableTargetSet for TargetSet; // HoprChannels method ids (TargetType.CHANNELS) bytes4 public constant REDEEM_TICKET_SELECTOR = HoprChannels.redeemTicketSafe.selector; bytes4 public constant CLOSE_INCOMING_CHANNEL_SELECTOR = HoprChannels.closeIncomingChannelSafe.selector; bytes4 public constant INITIATE_OUTGOING_CHANNEL_CLOSURE_SELECTOR = HoprChannels.initiateOutgoingChannelClosureSafe.selector; bytes4 public constant FINALIZE_OUTGOING_CHANNEL_CLOSURE_SELECTOR = HoprChannels.finalizeOutgoingChannelClosureSafe.selector; bytes4 public constant FUND_CHANNEL_SELECTOR = HoprChannels.fundChannelSafe.selector; // HoprToken method ids (TargetType.TOKEN). As HoprToken contract is in production, its ABI is static bytes4 public constant APPROVE_SELECTOR = IERC20.approve.selector; bytes4 public constant SEND_SELECTOR = IERC777.send.selector; event RevokedTarget(address indexed targetAddress); event ScopedTargetChannels(address indexed targetAddress, Target target); event ScopedTargetToken(address indexed targetAddress, Target target); event ScopedTargetSend(address indexed targetAddress, Target target); event ScopedGranularChannelCapability( address indexed targetAddress, bytes32 indexed channelId, bytes4 selector, GranularPermission permission ); event ScopedGranularTokenCapability( address indexed nodeAddress, address indexed targetAddress, address indexed recipientAddress, bytes4 selector, GranularPermission permission ); event ScopedGranularSendCapability( address indexed nodeAddress, address indexed recipientAddress, GranularPermission permission ); /// Sender is not a member of the role error NoMembership(); /// Arrays must be the same length error ArraysDifferentLength(); /// Arrays must not exceed the maximum length error ArrayTooLong(); /// Address cannot be zero error AddressIsZero(); /// Function signature too short error FunctionSignatureTooShort(); /// Role not allowed to delegate call to target address error DelegateCallNotAllowed(); /// Role not allowed to call target address error TargetAddressNotAllowed(); /// Role not allowed to send to target address error SendNotAllowed(); /// Role not allowed to use bytes for parameter error ParameterNotAllowed(); /// only multisend txs with an offset of 32 bytes are allowed error UnacceptableMultiSendOffset(); /// The provided calldata for execution is too short, or an OutOfBounds scoped parameter was configured error CalldataOutOfBounds(); // Default permission not acquired error DefaultPermissionRejected(); // Granular permission not acquired error GranularPermissionRejected(); // Node permission rejected error NodePermissionRejected(); // Permission not properly configured error PermissionNotConfigured(); // target is already scoped error TargetIsScoped(); // target is not yet scoped error TargetIsNotScoped(); // ====================================================== // ---------------------- CHECKERS ---------------------- // ====================================================== /** * @dev Checks the permission of a transaction execution based on the role membership and transaction details. * @param role The storage reference to the Role struct. * @param multisend The address of the multisend contract. * @param to The recipient address of the transaction. * @param value The value of the transaction. * @param data The transaction data. * @param operation The operation type of the transaction. */ function check( Role storage role, address multisend, address to, uint256 value, bytes calldata data, Enum.Operation operation ) internal view { if (multisend == to) { // here the operation should be delegate checkMultisendTransaction(role, data); } else { checkTransaction(role, to, value, data, operation); } } /** * @dev Splits a multisend data blob into transactions and forwards them to be checked. * @param role The storage reference to the Role struct. * @param data The packed transaction data (created by the `buildMultiSendSafeTx` utility function). */ function checkMultisendTransaction(Role storage role, bytes memory data) internal view { Enum.Operation operation; address to; uint256 value; bytes memory out; uint256 dataLength; uint256 offset; assembly { offset := mload(add(data, 36)) } if (offset != 32) { revert UnacceptableMultiSendOffset(); } // transaction data (1st tx operation) reads at byte 100, // 4 bytes (multisend_id) + 32 bytes (offset_multisend_data) + 32 bytes multisend_data_length // increment i by the transaction data length // + 85 bytes of the to, value, and operation bytes until we reach the end of the data for (uint256 i = 100; i < data.length; i += (85 + dataLength)) { assembly { // First byte of the data is the operation. // We shift by 248 bits (256 - 8 [operation byte]) right since mload will always load 32 bytes (a word). // This will also zero out unused data. operation := shr(0xf8, mload(add(data, i))) // We offset the load address by 1 byte (operation byte) // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out // unused data. to := shr(0x60, mload(add(data, add(i, 0x01)))) // We offset the load address by 21 byte (operation byte + 20 address bytes) value := mload(add(data, add(i, 0x15))) // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes) dataLength := mload(add(data, add(i, 0x35))) // load actual transaction data with an offset of 53 byte (operation byte + 20 address bytes + 32 value // bytes) out := add(data, add(i, 0x35)) } checkTransaction(role, to, value, out, operation); } } /** * @dev Main transaction to check the permission of transaction execution of a module. * @param role The storage reference to the Role struct. * @param targetAddress The address of the target contract. * @param value The value of the transaction. * @param data The transaction data. * @param operation The operation type of the transaction. */ function checkTransaction( Role storage role, address targetAddress, uint256 value, bytes memory data, Enum.Operation operation ) internal view { if (data.length != 0 && data.length < 4) { revert FunctionSignatureTooShort(); } Target target = role.targets.get(targetAddress); // target is in scope; delegate call is not allowed; value can only be sent with `SEND` checkExecutionOptions(value, operation, target); bytes4 functionSig = bytes4(data); // check default permissions and get the fallback permission TargetPermission defaultPermission = getDefaultPermission(data.length, target, functionSig); // allow early revert or early return if (defaultPermission == TargetPermission.BLOCK_ALL) { revert DefaultPermissionRejected(); } else if (defaultPermission == TargetPermission.ALLOW_ALL) { return; } GranularPermission granularPermission; // check function permission if (target.getTargetType() == TargetType.TOKEN) { // check with HoprToken contract granularPermission = checkHoprTokenParameters(role, keyForFunctions(targetAddress, functionSig), functionSig, data); } else if (target.getTargetType() == TargetType.CHANNELS) { // check with HoprChannels contract granularPermission = checkHoprChannelsParameters(role, keyForFunctions(targetAddress, functionSig), functionSig, data); } else if (target.getTargetType() == TargetType.SEND) { granularPermission = checkSendParameters(role, targetAddress); } // check permission result if ( granularPermission == GranularPermission.BLOCK || ( granularPermission == GranularPermission.NONE && defaultPermission == TargetPermission.SPECIFIC_FALLBACK_BLOCK ) ) { revert GranularPermissionRejected(); } else if ( granularPermission == GranularPermission.ALLOW || ( granularPermission == GranularPermission.NONE && defaultPermission == TargetPermission.SPECIFIC_FALLBACK_ALLOW ) ) { return; } else { revert PermissionNotConfigured(); } } /** * @dev Check if target is scoped; if the transaction can send along native tokens; if DelegatedCall is allowed. * @param value The value of the transaction. * @param operation The operation type of the transaction. * @param target The stored target */ function checkExecutionOptions(uint256 value, Enum.Operation operation, Target target) internal pure { if (target.getTargetClearance() != Clearance.FUNCTION) { revert TargetAddressNotAllowed(); } // delegate call is not allowed; if (operation == Enum.Operation.DelegateCall) { revert DelegateCallNotAllowed(); } // send native tokens is only available to a set of addresses if (value > 0 && !target.isTargetType(TargetType.SEND)) { revert SendNotAllowed(); } } /* * @dev Check parameters for HoprChannels capability * @param role reference to role storage * @param capabilityKey Key to the capability. * @param functionSig Function method ID * @param data payload (with function signature) */ function checkHoprChannelsParameters( Role storage role, bytes32 capabilityKey, bytes4 functionSig, bytes memory data ) internal view returns (GranularPermission) { // check the first two evm slots of data payload // according to the following ABIs // - fundChannelSafe(address self, address account, Balance amount) // src,dst // - redeemTicketSafe(address self, RedeemableTicket calldata redeemable) // dst,channelId // - initiateOutgoingChannelClosureSafe(address self, address destination) // src,dst // - closeIncomingChannelSafe(address self, address source) // dst,src // - finalizeOutgoingChannelClosureSafe(address self, address destination) // src,dst // - setCommitmentSafe(address self, address source, bytes32 newCommitment) // dst,src address self = pluckOneStaticAddress(0, data); // the first slot should always store the self address if (self != msg.sender) { revert NodePermissionRejected(); } bytes32 channelId; if (functionSig == REDEEM_TICKET_SELECTOR) { channelId = pluckOneBytes32(1, data); } else if (functionSig == CLOSE_INCOMING_CHANNEL_SELECTOR) { address source = pluckOneStaticAddress(1, data); channelId = getChannelId(source, self); } else if ( functionSig == INITIATE_OUTGOING_CHANNEL_CLOSURE_SELECTOR || functionSig == FINALIZE_OUTGOING_CHANNEL_CLOSURE_SELECTOR || functionSig == FUND_CHANNEL_SELECTOR ) { address destination = pluckOneStaticAddress(1, data); channelId = getChannelId(self, destination); } else { revert ParameterNotAllowed(); } return role.capabilities[capabilityKey][channelId]; } /* * @dev Will revert if a transaction has a parameter that is not allowed * @notice This function is invoked on non-HoprChannels contracts (i.e. HoprTokens) * @param role reference to role storage * @param capabilityKey Key to the capability. * @param functionSig Function method ID * @param data payload (with function signature) */ function checkHoprTokenParameters( Role storage role, bytes32 capabilityKey, bytes4 functionSig, bytes memory data ) internal view returns (GranularPermission) { // for APPROVE_SELECTOR the abi is (address, uint256) // for SEND_SELECTOR the abi is (address, uint256, bytes) // note that beneficiary could event be a CHANNELS target. // Calling send to a HoprChannels contract is equivalent to calling // fundChannel or fundChannelsMulti function. However, granular control is skipped! if (functionSig != APPROVE_SELECTOR && functionSig != SEND_SELECTOR) { revert ParameterNotAllowed(); } address beneficiary = pluckOneStaticAddress(0, data); bytes32 pairId = getChannelId(msg.sender, beneficiary); return role.capabilities[capabilityKey][pairId]; } /** * @dev Checks the parameters for sending native tokens. * @param role The Role storage instance. * @param targetAddress The target address for the send operation. */ function checkSendParameters(Role storage role, address targetAddress) internal view returns (GranularPermission) { bytes32 pairId = getChannelId(msg.sender, targetAddress); return role.capabilities[bytes32(0)][pairId]; } /** * @dev check the default target permission for target and for the function * returns the default permission * @param dataLength Length of data payload * @param target Taret of the operation * @param functionSig bytes4 method Id of the operation */ function getDefaultPermission( uint256 dataLength, Target target, bytes4 functionSig ) internal pure returns (TargetPermission) { // check default target permission TargetPermission defaultTargetPermission = target.getDefaultTargetPermission(); // early return when the permission allows if ( dataLength == 0 || functionSig == bytes4(0) || defaultTargetPermission == TargetPermission.ALLOW_ALL || defaultTargetPermission == TargetPermission.BLOCK_ALL ) { return defaultTargetPermission; } CapabilityPermission defaultFunctionPermission; if (functionSig == REDEEM_TICKET_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(0); } else if (functionSig == CLOSE_INCOMING_CHANNEL_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(2); } else if (functionSig == INITIATE_OUTGOING_CHANNEL_CLOSURE_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(3); } else if (functionSig == FINALIZE_OUTGOING_CHANNEL_CLOSURE_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(4); } else if (functionSig == FUND_CHANNEL_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(5); } else if (functionSig == APPROVE_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(7); } else if (functionSig == SEND_SELECTOR) { defaultFunctionPermission = target.getDefaultCapabilityPermissionAt(8); } else { revert ParameterNotAllowed(); } // only when function permission is not defined, use target default permission if (defaultFunctionPermission == CapabilityPermission.NONE) { return defaultTargetPermission; } else { return TargetUtils.convertFunctionToTargetPermission(defaultFunctionPermission); } } // ====================================================== // ----------------------- SETTERS ---------------------- // ====================================================== /** * @dev Revokes the target address from the Role by setting its clearance and target type to None. * @param role The storage reference to the Role struct. * @param targetAddress The address of the target to be revoked. */ function revokeTarget(Role storage role, address targetAddress) internal { bool result = role.targets.remove(targetAddress); if (result) { emit RevokedTarget(targetAddress); } else { revert TargetIsNotScoped(); } } /** * @dev Allows the target address to be scoped as a HoprToken (TOKEN) * by setting its clearance and target type accordingly. * @param role The storage reference to the Role struct. * @param target target to be scoped as a beneficiary of SEND. */ function scopeTargetToken(Role storage role, Target target) internal { address targetAddress = target.getTargetAddress(); if (targetAddress == address(0)) { revert AddressIsZero(); } // check targetAddress is not scoped if (role.targets.contains(targetAddress)) { revert TargetIsScoped(); } // force overwrite irrelevant defaults Target updatedTarget = target.forceWriteAsTargetType(TargetType.TOKEN); role.targets.add(updatedTarget); emit ScopedTargetToken(targetAddress, updatedTarget); } /** * @dev Allows the target address to be scoped as a HoprChannels contract (CHANNELS) * by setting its clearance and target type accordingly. * @param role The storage reference to the Role struct. * @param target target to be scoped as a beneficiary of SEND. */ function scopeTargetChannels(Role storage role, Target target) internal { address targetAddress = target.getTargetAddress(); if (targetAddress == address(0)) { revert AddressIsZero(); } // check targetAddress is not scoped if (role.targets.contains(targetAddress)) { revert TargetIsScoped(); } // force overwrite irrelevant defaults Target updatedTarget = target.forceWriteAsTargetType(TargetType.CHANNELS); role.targets.add(updatedTarget); emit ScopedTargetChannels(targetAddress, updatedTarget); } /** * @dev Allows the target address to be scoped as a beneficiary of SEND by setting its clearance and target type * accordingly. * @notice It overwrites the irrelevant fields in DefaultPermissions struct * @param role The storage reference to the Role struct. * @param target target to be scoped as a beneficiary of SEND. */ function scopeTargetSend(Role storage role, Target target) internal { address targetAddress = target.getTargetAddress(); if (targetAddress == address(0)) { revert AddressIsZero(); } // check targetAddress is not scoped if (role.targets.contains(targetAddress)) { revert TargetIsScoped(); } // force overwrite irrelevant defaults Target updatedTarget = target.forceWriteAsTargetType(TargetType.SEND); role.targets.add(updatedTarget); emit ScopedTargetSend(targetAddress, updatedTarget); } /** * @dev Sets permissions for a set of max. 7 functions on a scoped CHANNELS target. * @param role The storage reference to the Role struct. * @param targetAddress The address of the scoped CHANNELS target. * @param channelId The channelId of the scoped CHANNELS target. * @param encodedSigsPermissions encoded permission using encodeFunctionSigsAndPermissions */ function scopeChannelsCapabilities( Role storage role, address targetAddress, bytes32 channelId, bytes32 encodedSigsPermissions ) internal { (bytes4[] memory functionSigs, GranularPermission[] memory permissions) = HoprCapabilityPermissions.decodeFunctionSigsAndPermissions(encodedSigsPermissions, 7); for (uint256 i = 0; i < 7; i++) { if (functionSigs[i] != bytes4(0)) { bytes32 capabilityKey = keyForFunctions(targetAddress, functionSigs[i]); role.capabilities[capabilityKey][channelId] = permissions[i]; emit ScopedGranularChannelCapability(targetAddress, channelId, functionSigs[i], permissions[i]); } } } /** * @dev Sets the permission for a specific function on a scoped TOKEN target. * @notice As only two function signatures are allowed, the length is set to 2 * @param role The storage reference to the Role struct. * @param nodeAddress The address of the caller node. * @param targetAddress The address of the scoped TOKEN target. * @param beneficiary The beneficiary address for the scoped TOKEN target. * @param encodedSigsPermissions encoded permission using encodeFunctionSigsAndPermissions */ function scopeTokenCapabilities( Role storage role, address nodeAddress, address targetAddress, address beneficiary, bytes32 encodedSigsPermissions ) internal { (bytes4[] memory functionSigs, GranularPermission[] memory permissions) = HoprCapabilityPermissions.decodeFunctionSigsAndPermissions(encodedSigsPermissions, 2); for (uint256 i = 0; i < 2; i++) { if (functionSigs[i] != bytes4(0)) { bytes32 capabilityKey = keyForFunctions(targetAddress, functionSigs[i]); role.capabilities[capabilityKey][getChannelId(nodeAddress, targetAddress)] = permissions[i]; emit ScopedGranularTokenCapability( nodeAddress, targetAddress, beneficiary, functionSigs[i], permissions[i] ); } } } /** * @dev Sets the permission for sending native tokens to a specific beneficiary * @notice The capability ID for sending native tokens is bytes32(0x00) * @param nodeAddress The address of the caller node * @param beneficiary The beneficiary address for the scoped SEND target. * @param permission The permission to be set for the specific function. */ function scopeSendCapability( Role storage role, address nodeAddress, address beneficiary, GranularPermission permission ) internal { role.capabilities[bytes32(0)][getChannelId(nodeAddress, beneficiary)] = permission; emit ScopedGranularSendCapability(nodeAddress, beneficiary, permission); } // ====================================================== // ----------------------- HELPERS ---------------------- // ====================================================== function getChannelId(address source, address destination) internal pure returns (bytes32) { return keccak256(abi.encodePacked(source, destination)); } /** * @dev Retrieves a static address value from the given `data` byte array at the specified `index`. * @param data The byte array containing the data. * @param index The index of the static address value to retrieve. * @return addr The static address value at the specified index. */ function pluckOneStaticAddress(uint256 index, bytes memory data) internal pure returns (address) { // pre-check: is there a word available for the current parameter at argumentsBlock? if (data.length < 4 + index * 32 + 32) { revert CalldataOutOfBounds(); } uint256 offset = 4 + index * 32; address addr; assembly { // add 32 - jump over the length encoding of the data bytes array addr := mload(add(32, add(data, offset))) } return addr; } /** * @dev Extracts one bytes32 from the `data` byte array. * @param data The byte array containing the bytes32. * @param index The index of the static bytes32 value to retrieve. * @return by32 The second bytes32 extracted from the `data` byte array. */ function pluckOneBytes32(uint256 index, bytes memory data) internal pure returns (bytes32) { // pre-check: is there a word available for the current parameter at argumentsBlock? if (data.length < 4 + index * 32 + 32) { revert CalldataOutOfBounds(); } uint256 offset = 4 + index * 32; bytes32 by32; assembly { // add 32 - jump over the length encoding of the data bytes array by32 := mload(add(32, add(data, offset))) } return by32; } /** * @dev Returns the unique key for a function of a given `targetAddress`. * @param targetAddress The address of the target contract. * @param functionSig The function signature of the target function. * @return key The unique key representing the target function. */ function keyForFunctions(address targetAddress, bytes4 functionSig) internal pure returns (bytes32) { return bytes32(abi.encodePacked(targetAddress, functionSig)); } /** * @dev Returns arrays of bytes32 that concates function signatures (bytes4 = 32 bits) * together with granular permissions (per channel id or per beneficiary) (2 bits) * It can take maxinum 7 sets (256 / (32 + 2) ~= 7) of function signatures and permissions * @notice Signature encoding is right-padded; Index 0 is the left most and grows to the right * Permission encoding is left-padded; Index grows from right to the left. * Returns a bytes32 and length of sigature and permissions * @param functionSigs array of function signatures on target * @param permissions array of granular permissions on target */ function encodeFunctionSigsAndPermissions( bytes4[] memory functionSigs, GranularPermission[] memory permissions ) internal pure returns (bytes32 encoded, uint256 length) { uint256 len = functionSigs.length; if (len > 7) { revert ArrayTooLong(); } if (functionSigs.length != permissions.length) { revert ArraysDifferentLength(); } bytes32 val; // add function signatures for (uint256 i = 0; i < len; i++) { // first right shift (32 - 4) * 8 = 224 bits // then left shift (32 - 4 * i - 4) * 8 = (224 - 32 * i) bits val |= (bytes32(functionSigs[i]) >> 224) << (224 - (32 * i)); } for (uint256 i = 0; i < len; i++) { // shift by two bits val |= bytes32(uint256(permissions[i])) << (2 * i); } return (val, len); } /** * @dev Returns an bytes4 array which decodes from the combined encoding * of function signature and permissions. It can take maxinum 7 items. * Encoding of function signatures is right-padded, where indexes grow from right to left * Encoding of permissions is left-padded, where indexes grow from left to right * @param encoded encode permissions in bytes32 * @param length length of permissions */ function decodeFunctionSigsAndPermissions( bytes32 encoded, uint256 length ) internal pure returns (bytes4[] memory functionSigs, GranularPermission[] memory permissions) { if (length > 7) { revert ArrayTooLong(); } functionSigs = new bytes4[](length); permissions = new GranularPermission[](length); // decode function signature for (uint256 i = 0; i < length; i++) { // first right shift (32 - 4 * i - 4) * 8 = (224 - 32 * i) bits // then left shift (32 - 4) * 8 = 224 bits functionSigs[i] = bytes4((encoded >> (224 - (32 * i))) << 224); } // decode permissions for (uint256 j = 0; j < length; j++) { // first left shift 256 - 2 - 2 * j = 254 - 2 * j bits // then right shift 256 - 2 = 254 bits permissions[j] = GranularPermission(uint8((uint256(encoded) << (254 - (2 * j))) >> 254)); } } } /** * &&&& * &&&& * &&&& * &&&& &&&&&&&&& &&&&&&&&&&&& &&&&&&&&&&/ &&&&.&&&&&&&&& * &&&&&&&&& &&&&& &&&&&& &&&&&, &&&&& &&&&& &&&&&&&& &&&& * &&&&&& &&&& &&&&# &&&& &&&&& &&&&& &&&&&& &&&&& * &&&&& &&&&/ &&&& &&&& #&&&& &&&& &&&&& * &&&& &&&& &&&&& &&&& &&&& &&&&& &&&&& * %%%% /%%%% %%%%%% %%%%%% %%%% %%%%%%%%% %%%%% * %%%%% %%%% %%%%%%%%%%% %%%% %%%%%% %%%% * %%%% * %%%% * %%%% * * @title Permissioned capability-based module for HOPR nodes operations * * @dev Drawing inspiration from the `zodiac-modifier-roles-v1` `Roles.sol` contract, * this module removes target attribute and is dedicated for managing HOPR nodes * with capability based permissions. * Only those addresses that are added by the Safe can call execTransactionFromModule * A deployed Multisend contract address is included in the contract * Module can only execute DELEGATECALL to the Multisend contract * Module can execute CALLs to HoprChannels contracts * Module can execute CALLs to HoprToken contracts */ contract HoprNodeManagementModule is SimplifiedModule, IHoprNodeManagementModule { using TargetUtils for Target; using EnumerableTargetSet for TargetSet; bool public constant isHoprNodeManagementModule = true; // address to send delegated multisend calls to address public multisend; // from HoprCapabilityPermissions. This module is a Role where members are NODE_CHAIN_KEYs Role internal role; event SetMultisendAddress(address indexed multisendAddress); event NodeAdded(address indexed node); event NodeRemoved(address indexed node); // when the contract has already been initialized error AlreadyInitialized(); // when a node is a member of the role error WithMembership(); // Once module gets created, the ownership cannot be transferred error CannotChangeOwner(); // when safe and multisend address are the same error SafeMultisendSameAddress(); modifier nodeOnly() { if (!role.members[_msgSender()]) { revert HoprCapabilityPermissions.NoMembership(); } _; } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(bytes memory initParams) public initializer { (address _safe, address _multisend, bytes32 _defaultTokenChannelsTarget) = abi.decode(initParams, (address, address, bytes32)); // cannot accept a zero address as Safe or multisend contract if (_safe == address(0) || _multisend == address(0)) { revert HoprCapabilityPermissions.AddressIsZero(); } // cannot use same address for safe and multisend if (_safe == _multisend) { revert SafeMultisendSameAddress(); } // cannot setup again if it's been set up if (owner() != address(0) || multisend != address(0)) { revert AlreadyInitialized(); } // internally setTarget multisend = _multisend; _addChannelsAndTokenTarget(Target.wrap(uint256(_defaultTokenChannelsTarget))); // transfer ownership _transferOwnership(_safe); emit SetMultisendAddress(_multisend); } /** * @dev try to get target given a target address. It does not revert if `targetAddress` is not scoped * @param targetAddress Address of target */ function tryGetTarget(address targetAddress) external view returns (bool, Target) { return role.targets.tryGet(targetAddress); } /** * @dev get all the scoped targets */ function getTargets() external view returns (Target[] memory) { return role.targets.values(); } /** * @dev get the granular permission * @param capabilityKey Key to the capability. * @param pairId hashed value of the pair of concern, e.g. for channel `keccak256(src,dst)`, for token and send * `keccak256(owner,spender)` */ function getGranularPermissions(bytes32 capabilityKey, bytes32 pairId) external view returns (GranularPermission) { return role.capabilities[capabilityKey][pairId]; } /** * @dev check if an address has been included as a member (NODE_CHAIN_KEY) * @param nodeAddress address to be checked */ function isNode(address nodeAddress) external view returns (bool) { return role.members[nodeAddress]; } /** * @dev Add a node to be able to execute this module, to the target * @param nodeAddress address of node */ function addNode(address nodeAddress) external onlyOwner { _addNode(nodeAddress); } /** * @dev Remove a node from being able to execute this module, to the target * @param nodeAddress address of node */ function removeNode(address nodeAddress) external onlyOwner { // cannot move a node that's not added if (!role.members[nodeAddress]) { revert HoprCapabilityPermissions.NoMembership(); } role.members[nodeAddress] = false; emit NodeRemoved(nodeAddress); } /// @dev Set the address of the expected multisend library /// @notice Only callable by owner. /// @param _multisend address of the multisend library contract function setMultisend(address _multisend) external onlyOwner { multisend = _multisend; emit SetMultisendAddress(multisend); } /** * @dev Allows the target address to be scoped as a HoprChannels target * and its token as a HoprToken target. HoprToken address is obtained from * HoprChannels contract * @param defaultTarget The default target with default permissions for CHANNELS and TOKEN target */ function addChannelsAndTokenTarget(Target defaultTarget) external onlyOwner { _addChannelsAndTokenTarget(defaultTarget); } /** * @dev Include a node as a member, set its default SEND permissions */ function includeNode(Target nodeDefaultTarget) external onlyOwner { address nodeAddress = nodeDefaultTarget.getTargetAddress(); // add a node as a member _addNode(nodeAddress); // scope default capabilities HoprCapabilityPermissions.scopeTargetSend(role, nodeDefaultTarget); // scope granular capabilities to send native tokens to itself HoprCapabilityPermissions.scopeSendCapability(role, nodeAddress, nodeAddress, GranularPermission.ALLOW); } /** * @dev Scopes the target address as a HoprChannels target * @param defaultTarget The default target with default permissions for CHANNELS target */ function scopeTargetChannels(Target defaultTarget) external onlyOwner { HoprCapabilityPermissions.scopeTargetChannels(role, defaultTarget); } /** * @dev Scopes the target address as a HoprToken target * @param defaultTarget The default target with default permissions for TOKEN target */ function scopeTargetToken(Target defaultTarget) external onlyOwner { HoprCapabilityPermissions.scopeTargetToken(role, defaultTarget); } /** * @dev Scopes the target address as a Send target, so native tokens can be * transferred from the avatar to the target. * @notice Only member is allowed to be a beneficiary * @param defaultTarget The default target with default permissions for SEND target */ function scopeTargetSend(Target defaultTarget) external onlyOwner { address beneficiaryAddress = defaultTarget.getTargetAddress(); if (!role.members[beneficiaryAddress]) { revert HoprCapabilityPermissions.NoMembership(); } HoprCapabilityPermissions.scopeTargetSend(role, defaultTarget); } /** * @dev Revokes the target address from the scope * @param targetAddress The address of the target to be revoked. */ function revokeTarget(address targetAddress) external onlyOwner { HoprCapabilityPermissions.revokeTarget(role, targetAddress); } /** * @dev Sets the permission for a set of functions on a scoped CHANNELS target for a given channel * @notice it can batch maxinum 7 capabilities. * Encoding of function signatures is right-padded, where indexes grow from left to right * Encoding of permissions is left-padded, where indexes grow from left to right * @param targetAddress The address of the scoped HoprChannels target. * @param channelId The channelId of the scoped HoprChannels target. * @param encodedSigsPermissions The encoded function signatures and permissions */ function scopeChannelsCapabilities( address targetAddress, bytes32 channelId, bytes32 encodedSigsPermissions ) external onlyOwner { HoprCapabilityPermissions.scopeChannelsCapabilities(role, targetAddress, channelId, encodedSigsPermissions); } /** * @dev Sets the permissions for functions on a scoped HoprToken target for different beneficiaries * @notice it can batch maxinum 7 capabilities. * Encoding of function signatures is right-padded, where indexes grow from left to right * Encoding of permissions is left-padded, where indexes grow from left to right * @param nodeAddress The address of the caller node * @param targetAddress The address of the scoped HoprToken target. * @param beneficiary The beneficiary address for the scoped HoprToken target. * @param encodedSigsPermissions The encoded function signatures and permissions */ function scopeTokenCapabilities( address nodeAddress, address targetAddress, address beneficiary, bytes32 encodedSigsPermissions ) external onlyOwner { HoprCapabilityPermissions.scopeTokenCapabilities( role, nodeAddress, targetAddress, beneficiary, encodedSigsPermissions ); } /** * @dev Sets the permission for sending native tokens to a specific beneficiary * @param nodeAddress The address of the caller node * @param beneficiary The beneficiary address for the scoped Send target. * @param permission The permission to be set for the specific function. */ function scopeSendCapability( address nodeAddress, address beneficiary, GranularPermission permission ) external onlyOwner { HoprCapabilityPermissions.scopeSendCapability(role, nodeAddress, beneficiary, permission); } // =========================================================== // ------------------------ UTILITIES ------------------------ // =========================================================== /** * @dev help encode function permissions into a bytes32 * @param functionSigs array of function signatures on target * @param permissions array of granular permissions on target */ function encodeFunctionSigsAndPermissions( bytes4[] memory functionSigs, GranularPermission[] memory permissions ) external pure returns (bytes32 encoded, uint256 length) { return HoprCapabilityPermissions.encodeFunctionSigsAndPermissions(functionSigs, permissions); } /** * @dev help encode function permissions into a bytes32 * @param encoded encode permissions in bytes32 * @param length length of permissions */ function decodeFunctionSigsAndPermissions( bytes32 encoded, uint256 length ) external pure returns (bytes4[] memory functionSigs, GranularPermission[] memory permissions) { return HoprCapabilityPermissions.decodeFunctionSigsAndPermissions(encoded, length); } // =========================================================== // ----------------------- INHERITANCE ----------------------- // =========================================================== /// @dev Passes a transaction to the modifier. /// @param to Destination address of module transaction /// @param value Ether value of module transaction /// @param data Data payload of module transaction /// @param operation Operation type of module transaction /// @notice Can only be called by enabled modules function execTransactionFromModule( address to, uint256 value, bytes calldata data, Enum.Operation operation ) public nodeOnly returns (bool success) { HoprCapabilityPermissions.check(role, multisend, to, value, data, operation); return exec(to, value, data, operation); } /// @dev Passes a transaction to the modifier, expects return data. /// @param to Destination address of module transaction /// @param value Ether value of module transaction /// @param data Data payload of module transaction /// @param operation Operation type of module transaction /// @notice Can only be called by enabled modules function execTransactionFromModuleReturnData( address to, uint256 value, bytes calldata data, Enum.Operation operation ) public nodeOnly returns (bool, bytes memory) { HoprCapabilityPermissions.check(role, multisend, to, value, data, operation); return execAndReturnData(to, value, data, operation); } /** * @dev Private function to scope a channel and token target. * @param defaultTarget The default target with default permissions for CHANNELS and TOKEN target */ function _addChannelsAndTokenTarget(Target defaultTarget) private { // get channels andtokens contract address hoprChannelsAddress = defaultTarget.getTargetAddress(); address hoprTokenAddress = address(HoprChannels(hoprChannelsAddress).token()); // add default scope for Channels TargetType, with the build target for hoprChannels address HoprCapabilityPermissions.scopeTargetChannels(role, defaultTarget.forceWriteTargetAddress(hoprChannelsAddress)); // add default scope for Token TargetType HoprCapabilityPermissions.scopeTargetToken(role, defaultTarget.forceWriteTargetAddress(hoprTokenAddress)); } /** * @dev private function to add a node as a member of the module */ function _addNode(address nodeAddress) private { // cannot add a node that's added if (role.members[nodeAddress]) { revert WithMembership(); } role.members[nodeAddress] = true; emit NodeAdded(nodeAddress); } }
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressIsZero","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ArrayTooLong","type":"error"},{"inputs":[],"name":"ArraysDifferentLength","type":"error"},{"inputs":[],"name":"CalldataOutOfBounds","type":"error"},{"inputs":[],"name":"CannotChangeOwner","type":"error"},{"inputs":[],"name":"DefaultPermissionRejected","type":"error"},{"inputs":[],"name":"DelegateCallNotAllowed","type":"error"},{"inputs":[],"name":"FunctionSignatureTooShort","type":"error"},{"inputs":[],"name":"GranularPermissionRejected","type":"error"},{"inputs":[],"name":"NoMembership","type":"error"},{"inputs":[],"name":"NodePermissionRejected","type":"error"},{"inputs":[],"name":"NonExistentKey","type":"error"},{"inputs":[],"name":"ParameterNotAllowed","type":"error"},{"inputs":[],"name":"PermissionNotConfigured","type":"error"},{"inputs":[],"name":"PermissionNotFound","type":"error"},{"inputs":[],"name":"SafeMultisendSameAddress","type":"error"},{"inputs":[],"name":"SendNotAllowed","type":"error"},{"inputs":[],"name":"TargetAddressNotAllowed","type":"error"},{"inputs":[],"name":"TargetIsNotScoped","type":"error"},{"inputs":[],"name":"TargetIsScoped","type":"error"},{"inputs":[],"name":"TooManyCapabilities","type":"error"},{"inputs":[],"name":"UnacceptableMultiSendOffset","type":"error"},{"inputs":[],"name":"WithMembership","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[],"name":"ExecutionFailure","type":"event"},{"anonymous":false,"inputs":[],"name":"ExecutionSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"node","type":"address"}],"name":"NodeAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"node","type":"address"}],"name":"NodeRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"multisendAddress","type":"address"}],"name":"SetMultisendAddress","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[{"internalType":"Target","name":"defaultTarget","type":"uint256"}],"name":"addChannelsAndTokenTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nodeAddress","type":"address"}],"name":"addNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"encoded","type":"bytes32"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"decodeFunctionSigsAndPermissions","outputs":[{"internalType":"bytes4[]","name":"functionSigs","type":"bytes4[]"},{"internalType":"enum GranularPermission[]","name":"permissions","type":"uint8[]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4[]","name":"functionSigs","type":"bytes4[]"},{"internalType":"enum GranularPermission[]","name":"permissions","type":"uint8[]"}],"name":"encodeFunctionSigsAndPermissions","outputs":[{"internalType":"bytes32","name":"encoded","type":"bytes32"},{"internalType":"uint256","name":"length","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModule","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModuleReturnData","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"capabilityKey","type":"bytes32"},{"internalType":"bytes32","name":"pairId","type":"bytes32"}],"name":"getGranularPermissions","outputs":[{"internalType":"enum GranularPermission","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTargets","outputs":[{"internalType":"Target[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Target","name":"nodeDefaultTarget","type":"uint256"}],"name":"includeNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"initParams","type":"bytes"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isHoprNodeManagementModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nodeAddress","type":"address"}],"name":"isNode","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"multisend","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nodeAddress","type":"address"}],"name":"removeNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"targetAddress","type":"address"}],"name":"revokeTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"targetAddress","type":"address"},{"internalType":"bytes32","name":"channelId","type":"bytes32"},{"internalType":"bytes32","name":"encodedSigsPermissions","type":"bytes32"}],"name":"scopeChannelsCapabilities","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nodeAddress","type":"address"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"enum GranularPermission","name":"permission","type":"uint8"}],"name":"scopeSendCapability","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Target","name":"defaultTarget","type":"uint256"}],"name":"scopeTargetChannels","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Target","name":"defaultTarget","type":"uint256"}],"name":"scopeTargetSend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Target","name":"defaultTarget","type":"uint256"}],"name":"scopeTargetToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nodeAddress","type":"address"},{"internalType":"address","name":"targetAddress","type":"address"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"bytes32","name":"encodedSigsPermissions","type":"bytes32"}],"name":"scopeTokenCapabilities","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_multisend","type":"address"}],"name":"setMultisend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"targetAddress","type":"address"}],"name":"tryGetTarget","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"Target","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"}]
Loading...
Loading
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.