xDAI Price: $1.00 (+0.00%)
Gas: 1.1 GWei

Contract

0xc9bcEa4f1cbF6f7EC91ec8718B17144D40aB1A61

Overview

xDAI Balance

Gnosis Chain LogoGnosis Chain LogoGnosis Chain Logo0 xDAI

xDAI Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Exec Transaction...384491982025-02-08 0:42:352 mins ago1738975355IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680783
Exec Transaction...384490112025-02-08 0:26:3518 mins ago1738974395IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000548073
Exec Transaction...384490112025-02-08 0:26:3518 mins ago1738974395IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680833
Exec Transaction...384487872025-02-08 0:07:3537 mins ago1738973255IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680593
Exec Transaction...384487292025-02-08 0:02:3542 mins ago1738972955IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680743
Exec Transaction...384486702025-02-07 23:57:3547 mins ago1738972655IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.00068083
Exec Transaction...384485222025-02-07 23:44:351 hr ago1738971875IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000548023
Exec Transaction...384485222025-02-07 23:44:351 hr ago1738971875IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680763
Exec Transaction...384482322025-02-07 23:19:351 hr ago1738970375IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680723
Exec Transaction...384481962025-02-07 23:16:351 hr ago1738970195IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680723
Exec Transaction...384481252025-02-07 23:10:351 hr ago1738969835IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680643
Exec Transaction...384479362025-02-07 22:54:351 hr ago1738968875IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680723
Exec Transaction...384479242025-02-07 22:53:351 hr ago1738968815IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680733
Exec Transaction...384477242025-02-07 22:36:352 hrs ago1738967795IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680713
Exec Transaction...384475472025-02-07 22:21:352 hrs ago1738966895IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680853
Exec Transaction...384474642025-02-07 22:14:352 hrs ago1738966475IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680823
Exec Transaction...384474412025-02-07 22:12:352 hrs ago1738966355IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680853
Exec Transaction...384473462025-02-07 22:04:352 hrs ago1738965875IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680793
Exec Transaction...384472642025-02-07 21:57:352 hrs ago1738965455IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680733
Exec Transaction...384469332025-02-07 21:29:353 hrs ago1738963775IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680663
Exec Transaction...384469212025-02-07 21:28:353 hrs ago1738963715IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.00068073
Exec Transaction...384468142025-02-07 21:19:353 hrs ago1738963175IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.00068073
Exec Transaction...384467792025-02-07 21:16:353 hrs ago1738962995IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680843
Exec Transaction...384467552025-02-07 21:14:353 hrs ago1738962875IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.00068063
Exec Transaction...384464042025-02-07 20:44:354 hrs ago1738961075IN
0xc9bcEa4f...D40aB1A61
0 xDAI0.000680733.00000001
View all transactions

Latest 1 internal transaction

Parent Transaction Hash Block From To
360854902024-09-19 19:05:15141 days ago1726772715  Contract Creation0 xDAI
Loading...
Loading

Minimal Proxy Contract for 0xb7397c218766ebe6a1a634df523a1a7e412e67ea

Contract Name:
HoprNodeManagementModule

Compiler Version
v0.8.19+commit.7dd6d404

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion, GNU LGPLv3 license

Contract Source Code (Solidity)

Decompile Bytecode Similar Contracts
/**
 *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

[{"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"}]

Block Transaction Gas Used Reward
view all blocks validated

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ 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.