Latest 25 from a total of 764 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Exec Transaction... | 43892927 | 30 days ago | IN | 0 XDAI | 0.00023022 | ||||
| Exec Transaction... | 43892927 | 30 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43858730 | 33 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43846422 | 33 days ago | IN | 0 XDAI | 0.00002268 | ||||
| Exec Transaction... | 43839652 | 34 days ago | IN | 0 XDAI | 0.00002268 | ||||
| Exec Transaction... | 43811368 | 35 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43727315 | 40 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43631444 | 46 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43542132 | 52 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43482597 | 55 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43467442 | 56 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43425069 | 59 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43415262 | 59 days ago | IN | 0 XDAI | 0.00023022 | ||||
| Exec Transaction... | 43415262 | 59 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43361115 | 62 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43289346 | 67 days ago | IN | 0 XDAI | 0.00002268 | ||||
| Exec Transaction... | 43283131 | 67 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43276310 | 67 days ago | IN | 0 XDAI | 0.00002339 | ||||
| Exec Transaction... | 43206213 | 72 days ago | IN | 0 XDAI | 0.00002336 | ||||
| Exec Transaction... | 43173045 | 74 days ago | IN | 0 XDAI | 0.00002269 | ||||
| Exec Transaction... | 43166835 | 74 days ago | IN | 0 XDAI | 0.00002282 | ||||
| Exec Transaction... | 43155467 | 75 days ago | IN | 0 XDAI | 0.00001717 | ||||
| Exec Transaction... | 43155412 | 75 days ago | IN | 0 XDAI | 0.00002059 | ||||
| Exec Transaction... | 43155408 | 75 days ago | IN | 0 XDAI | 0.00000825 | ||||
| Exec Transaction... | 43151334 | 75 days ago | IN | 0 XDAI | 0.00002279 |
Latest 1 internal transaction
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 36250366 | 486 days ago | Contract Creation | 0 XDAI |
Cross-Chain Transactions
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);
}
}Contract ABI
API[{"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
Net Worth in USD
$0.00
Net Worth in XDAI
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ 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.