xDAI Price: $0.999508 (+0.01%)

Contract

0x77C9414043d27fdC98A6A2d73fc77b9b383092a7

Overview

XDAI Balance

Gnosis Chain LogoGnosis Chain LogoGnosis Chain Logo0 XDAI

XDAI Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

> 10 Token Transfers found.

View more zero value Internal Transactions in Advanced View mode

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
HoprChannels

Compiler Version
v0.8.19+commit.7dd6d404

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion, GNU LGPLv3 license
/**
 *Submitted for verification at gnosisscan.io on 2023-08-30
*/

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.19;

// 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);

        // 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(
                abi.encode(
                    // ledger feed must be unique
                    ledgerDomainSeparator,
                    // Allows the verifier to detect up until which block the snapshot includes state changes
                    block.number,
                    // Bind result to previous root
                    latestRoot.rootHash,
                    // Information about the happened state change
                    keccak256(payload)
                )
            )
        );

        if (createSnapshot) {
            latestSnapshotRoot = latestRoot;
            latestRoot.timestamp = uint32(block.timestamp);
        }
    }
}

/**
 * @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()`
 */

/**
 * @title Enum - Collection of enums used in Safe contracts.
 * @author Richard Meissner - @rmeissner
 */
abstract contract Enum {
    enum Operation {
        Call,
        DelegateCall
    }
}

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);
}

/**
 * @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 ONE_HOUR = 60 * 60 * 1000; // in milliseconds

uint256 constant INDEX_SNAPSHOT_INTERVAL = ONE_HOUR;

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);
    }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"HoprChannels.Timestamp","name":"_noticePeriodChannelClosure","type":"uint32"},{"internalType":"contract HoprNodeSafeRegistry","name":"_safeRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BalanceExceedsGlobalPerChannelAllowance","type":"error"},{"inputs":[],"name":"ContractNotResponsible","type":"error"},{"inputs":[],"name":"InsufficientChannelBalance","type":"error"},{"inputs":[],"name":"InvalidAggregatedTicketInterval","type":"error"},{"inputs":[],"name":"InvalidBalance","type":"error"},{"inputs":[],"name":"InvalidCurvePoint","type":"error"},{"inputs":[],"name":"InvalidFieldElement","type":"error"},{"inputs":[],"name":"InvalidNoticePeriod","type":"error"},{"inputs":[],"name":"InvalidPointWitness","type":"error"},{"inputs":[],"name":"InvalidSafeAddress","type":"error"},{"inputs":[],"name":"InvalidTicketSignature","type":"error"},{"inputs":[],"name":"InvalidTokenRecipient","type":"error"},{"inputs":[],"name":"InvalidTokensReceivedUsage","type":"error"},{"inputs":[],"name":"InvalidVRFProof","type":"error"},{"inputs":[],"name":"MultiSigUninitialized","type":"error"},{"inputs":[],"name":"NoticePeriodNotDue","type":"error"},{"inputs":[],"name":"SourceEqualsDestination","type":"error"},{"inputs":[],"name":"TicketIsNotAWin","type":"error"},{"inputs":[],"name":"TokenTransferFailed","type":"error"},{"inputs":[{"internalType":"string","name":"reason","type":"string"}],"name":"WrongChannelState","type":"error"},{"inputs":[],"name":"WrongToken","type":"error"},{"inputs":[{"internalType":"string","name":"reason","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"channelId","type":"bytes32"},{"indexed":false,"internalType":"HoprChannels.Balance","name":"newBalance","type":"uint96"}],"name":"ChannelBalanceDecreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"channelId","type":"bytes32"},{"indexed":false,"internalType":"HoprChannels.Balance","name":"newBalance","type":"uint96"}],"name":"ChannelBalanceIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"channelId","type":"bytes32"}],"name":"ChannelClosed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"source","type":"address"},{"indexed":true,"internalType":"address","name":"destination","type":"address"}],"name":"ChannelOpened","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"domainSeparator","type":"bytes32"}],"name":"DomainSeparatorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"ledgerDomainSeparator","type":"bytes32"}],"name":"LedgerDomainSeparatorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"channelId","type":"bytes32"},{"indexed":false,"internalType":"HoprChannels.Timestamp","name":"closureTime","type":"uint32"}],"name":"OutgoingChannelClosureInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"channelId","type":"bytes32"},{"indexed":false,"internalType":"HoprChannels.TicketIndex","name":"newTicketIndex","type":"uint48"}],"name":"TicketRedeemed","type":"event"},{"inputs":[],"name":"ERC777_HOOK_FUND_CHANNEL_MULTI_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERC777_HOOK_FUND_CHANNEL_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEDGER_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_USED_BALANCE","outputs":[{"internalType":"HoprChannels.Balance","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_USED_BALANCE","outputs":[{"internalType":"HoprChannels.Balance","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKENS_RECIPIENT_INTERFACE_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_currentBlockTimestamp","outputs":[{"internalType":"HoprChannels.Timestamp","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"source","type":"address"},{"internalType":"address","name":"destination","type":"address"}],"name":"_getChannelId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"bytes32","name":"channelId","type":"bytes32"},{"internalType":"HoprChannels.Balance","name":"amount","type":"uint96"},{"internalType":"HoprChannels.TicketIndex","name":"ticketIndex","type":"uint48"},{"internalType":"HoprChannels.TicketIndexOffset","name":"indexOffset","type":"uint32"},{"internalType":"HoprChannels.ChannelEpoch","name":"epoch","type":"uint24"},{"internalType":"HoprChannels.WinProb","name":"winProb","type":"uint56"}],"internalType":"struct HoprChannels.TicketData","name":"data","type":"tuple"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"vs","type":"bytes32"}],"internalType":"struct HoprCrypto.CompactSignature","name":"signature","type":"tuple"},{"internalType":"uint256","name":"porSecret","type":"uint256"}],"internalType":"struct HoprChannels.RedeemableTicket","name":"redeemable","type":"tuple"}],"name":"_getTicketHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"ticketHash","type":"bytes32"},{"components":[{"components":[{"internalType":"bytes32","name":"channelId","type":"bytes32"},{"internalType":"HoprChannels.Balance","name":"amount","type":"uint96"},{"internalType":"HoprChannels.TicketIndex","name":"ticketIndex","type":"uint48"},{"internalType":"HoprChannels.TicketIndexOffset","name":"indexOffset","type":"uint32"},{"internalType":"HoprChannels.ChannelEpoch","name":"epoch","type":"uint24"},{"internalType":"HoprChannels.WinProb","name":"winProb","type":"uint56"}],"internalType":"struct HoprChannels.TicketData","name":"data","type":"tuple"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"vs","type":"bytes32"}],"internalType":"struct HoprCrypto.CompactSignature","name":"signature","type":"tuple"},{"internalType":"uint256","name":"porSecret","type":"uint256"}],"internalType":"struct HoprChannels.RedeemableTicket","name":"redeemable","type":"tuple"},{"components":[{"internalType":"uint256","name":"vx","type":"uint256"},{"internalType":"uint256","name":"vy","type":"uint256"},{"internalType":"uint256","name":"s","type":"uint256"},{"internalType":"uint256","name":"h","type":"uint256"},{"internalType":"uint256","name":"sBx","type":"uint256"},{"internalType":"uint256","name":"sBy","type":"uint256"},{"internalType":"uint256","name":"hVx","type":"uint256"},{"internalType":"uint256","name":"hVy","type":"uint256"}],"internalType":"struct HoprCrypto.VRFParameters","name":"params","type":"tuple"}],"name":"_isWinningTicket","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"interfaceHash","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"canImplementInterfaceForAddress","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"channels","outputs":[{"internalType":"HoprChannels.Balance","name":"balance","type":"uint96"},{"internalType":"HoprChannels.TicketIndex","name":"ticketIndex","type":"uint48"},{"internalType":"HoprChannels.Timestamp","name":"closureTime","type":"uint32"},{"internalType":"HoprChannels.ChannelEpoch","name":"epoch","type":"uint24"},{"internalType":"enum HoprChannels.ChannelStatus","name":"status","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"source","type":"address"}],"name":"closeIncomingChannel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"self","type":"address"},{"internalType":"address","name":"source","type":"address"}],"name":"closeIncomingChannelSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"destination","type":"address"}],"name":"finalizeOutgoingChannelClosure","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"self","type":"address"},{"internalType":"address","name":"destination","type":"address"}],"name":"finalizeOutgoingChannelClosureSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"HoprChannels.Balance","name":"amount","type":"uint96"}],"name":"fundChannel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"self","type":"address"},{"internalType":"address","name":"account","type":"address"},{"internalType":"HoprChannels.Balance","name":"amount","type":"uint96"}],"name":"fundChannelSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"destination","type":"address"}],"name":"initiateOutgoingChannelClosure","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"self","type":"address"},{"internalType":"address","name":"destination","type":"address"}],"name":"initiateOutgoingChannelClosureSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ledgerDomainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"noticePeriodChannelClosure","outputs":[{"internalType":"HoprChannels.Timestamp","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"bytes32","name":"channelId","type":"bytes32"},{"internalType":"HoprChannels.Balance","name":"amount","type":"uint96"},{"internalType":"HoprChannels.TicketIndex","name":"ticketIndex","type":"uint48"},{"internalType":"HoprChannels.TicketIndexOffset","name":"indexOffset","type":"uint32"},{"internalType":"HoprChannels.ChannelEpoch","name":"epoch","type":"uint24"},{"internalType":"HoprChannels.WinProb","name":"winProb","type":"uint56"}],"internalType":"struct HoprChannels.TicketData","name":"data","type":"tuple"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"vs","type":"bytes32"}],"internalType":"struct HoprCrypto.CompactSignature","name":"signature","type":"tuple"},{"internalType":"uint256","name":"porSecret","type":"uint256"}],"internalType":"struct HoprChannels.RedeemableTicket","name":"redeemable","type":"tuple"},{"components":[{"internalType":"uint256","name":"vx","type":"uint256"},{"internalType":"uint256","name":"vy","type":"uint256"},{"internalType":"uint256","name":"s","type":"uint256"},{"internalType":"uint256","name":"h","type":"uint256"},{"internalType":"uint256","name":"sBx","type":"uint256"},{"internalType":"uint256","name":"sBy","type":"uint256"},{"internalType":"uint256","name":"hVx","type":"uint256"},{"internalType":"uint256","name":"hVy","type":"uint256"}],"internalType":"struct HoprCrypto.VRFParameters","name":"params","type":"tuple"}],"name":"redeemTicket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"self","type":"address"},{"components":[{"components":[{"internalType":"bytes32","name":"channelId","type":"bytes32"},{"internalType":"HoprChannels.Balance","name":"amount","type":"uint96"},{"internalType":"HoprChannels.TicketIndex","name":"ticketIndex","type":"uint48"},{"internalType":"HoprChannels.TicketIndexOffset","name":"indexOffset","type":"uint32"},{"internalType":"HoprChannels.ChannelEpoch","name":"epoch","type":"uint24"},{"internalType":"HoprChannels.WinProb","name":"winProb","type":"uint56"}],"internalType":"struct HoprChannels.TicketData","name":"data","type":"tuple"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"vs","type":"bytes32"}],"internalType":"struct HoprCrypto.CompactSignature","name":"signature","type":"tuple"},{"internalType":"uint256","name":"porSecret","type":"uint256"}],"internalType":"struct HoprChannels.RedeemableTicket","name":"redeemable","type":"tuple"},{"components":[{"internalType":"uint256","name":"vx","type":"uint256"},{"internalType":"uint256","name":"vy","type":"uint256"},{"internalType":"uint256","name":"s","type":"uint256"},{"internalType":"uint256","name":"h","type":"uint256"},{"internalType":"uint256","name":"sBx","type":"uint256"},{"internalType":"uint256","name":"sBy","type":"uint256"},{"internalType":"uint256","name":"hVx","type":"uint256"},{"internalType":"uint256","name":"hVy","type":"uint256"}],"internalType":"struct HoprCrypto.VRFParameters","name":"params","type":"tuple"}],"name":"redeemTicketSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"userData","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"tokensReceived","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateDomainSeparator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateLedgerDomainSeparator","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6004805460ff60a01b191690556000610140819052610154819052610160819052610174819052604061012081905260a08190526101a08290526101b49190915260286101808190526101c890915260c0523480156200005e57600080fd5b5060405162004903380380620049038339810160408190526200008191620004c5565b6236ee8060808190526040516001600160601b03193060601b16602082015260340160408051601f19818403018152919052805160209182012063ffffffff4216600160e01b02911c17600155620000d86200022b565b508163ffffffff16600003620001015760405163f9ee910760e01b815260040160405180910390fd5b6001600160a01b0383166200015c5760405162461bcd60e51b815260206004820152601760248201527f746f6b656e206d757374206e6f7420626520656d707479000000000000000000604482015260640160405180910390fd5b62000167816200032a565b6001600160a01b03831660e05263ffffffff8216610100526040516329965a1d60e01b815230600482018190527fb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b60248301526044820152731820a4b7618bde71dce8cdc73aab6c95905fad24906329965a1d90606401600060405180830381600087803b158015620001f957600080fd5b505af11580156200020e573d6000803e3d6000fd5b5050505062000222620003a660201b60201c565b50505062000521565b604080518082018252600a8152692437b8392632b233b2b960b11b6020918201528151808301835260058152640312e302e360dc1b908201528151600080516020620048e3833981519152818301527f6cd681790c78c220517b099a737f8e85f69e797abe4e2910fb189b61db4bf2cd818401527f06c015bd22b4c69690933c1058878ebdfef31f9aaae40bbe86d8a09fe1b2972c60608201524660808201523060a0808301919091528351808303909101815260c0909101909252815191012060035481146200032757600381905560405181907fa43fad83920fd09445855e854e73c9c532e17402c9ceb09993a2392843a5bdb990600090a25b50565b600454600160a01b900460ff1615620003555760405162dc149f60e41b815260040160405180910390fd5b6001600160a01b0381166200037d5760405163474ebe2f60e11b815260040160405180910390fd5b600480546001600160a01b039092166001600160a81b031990921691909117600160a01b179055565b604080518082018252600c81526b486f70724368616e6e656c7360a01b6020918201528151808301835260058152640322e302e360dc1b908201528151600080516020620048e3833981519152918101919091527f84e6908f343601d9ce9fb60d8250394eb8a51c56f1876bc1e017c97acd6567f2918101919091527fb4bcb154e38601c389396fa918314da42d4626f13ef6d0ceb07e5f5d26b2fbc360608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905060055481146200032757600581905560405181907f771f5240ae5fd8a7640d3fb82fa70aab2fb1dbf35f2ef464f8509946717664c590600090a250565b6001600160a01b03811681146200032757600080fd5b600080600060608486031215620004db57600080fd5b8351620004e881620004af565b602085015190935063ffffffff811681146200050357600080fd5b60408501519092506200051681620004af565b809150509250925092565b60805160a05160c05160e0516101005161433e620005a5600039600081816103d9015261262d0152600081816104d701528181610566015281816109120152818161160101528181612089015281816122f801526124dc0152600081816102a801526105d501526000818161032e015261073001526000612751015261433e6000f3fe608060405234801561001057600080fd5b50600436106101e45760003560e01c80637c8e28da1161010f578063c966c4fe116100a2578063fc0c546a11610071578063fc0c546a146104d2578063fc55309a14610511578063fcb7796f14610524578063ffa1ad741461053757600080fd5b8063c966c4fe14610487578063dc96fd5014610490578063ddad190214610498578063f698da25146104c957600080fd5b8063ac9650d8116100de578063ac9650d81461043b578063b920deed1461045b578063bda65f4514610461578063be9babdc1461047457600080fd5b80637c8e28da146103c157806387352d65146103d457806389ccfe89146104105780638c3710c91461041857600080fd5b806329392e3211610187578063651514bf11610156578063651514bf146102ef57806372581cc01461030257806378d8016d146103295780637a7ebd7b1461035057600080fd5b806329392e321461028357806344dae6f8146102a357806354a2edf5146102ca5780635d2f07c5146102dd57600080fd5b80631a7ffe7a116101c35780631a7ffe7a1461022457806323cb3ac01461023757806324086cc21461024a578063249cb3fa1461027057600080fd5b806223de29146101e95780630abec58f146101fe5780630cd88d7214610211575b600080fd5b6101fc6101f7366004613a87565b61055b565b005b6101fc61020c366004613b54565b610817565b6101fc61021f366004613bc7565b6109af565b6101fc610232366004613c07565b610a80565b6101fc610245366004613c07565b610b50565b61025d610258366004613c2b565b610c1d565b6040519081526020015b60405180910390f35b61025d61027e366004613c48565b610d8a565b61028b600181565b6040516001600160601b039091168152602001610267565b61025d7f000000000000000000000000000000000000000000000000000000000000000081565b6101fc6102d8366004613c78565b610de4565b61028b6a084595161401484a00000081565b6101fc6102fd366004613c78565b610eb9565b61025d7fb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b81565b61025d7f000000000000000000000000000000000000000000000000000000000000000081565b6103b061035e366004613ca6565b6006602052600090815260409020546001600160601b03811690600160601b810465ffffffffffff1690600160901b810463ffffffff1690600160b01b810462ffffff1690600160c81b900460ff1685565b604051610267959493929190613cd5565b6101fc6103cf366004613c07565b610f89565b6103fb7f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff9091168152602001610267565b6101fc611056565b61042b610426366004613d38565b61116f565b6040519015158152602001610267565b61044e610449366004613d5f565b6111f1565b6040516102679190613e24565b426103fb565b6101fc61046f366004613c78565b6112e6565b61025d610482366004613c78565b6113b6565b61025d60035481565b6101fc6113fb565b6104bc604051806040016040528060058152602001640312e302e360dc1b81525081565b6040516102679190613e86565b61025d60055481565b6104f97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610267565b6101fc61051f366004613e99565b611509565b6101fc610532366004613ece565b61169c565b6104bc604051806040016040528060058152602001640322e302e360dc1b81525081565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105a457604051635079ff7560e11b815260040160405180910390fd5b6001600160a01b03861630146105cd57604051631738922160e31b815260040160405180910390fd5b821561080d577f0000000000000000000000000000000000000000000000000000000000000000830361072e576001600160601b038511156106225760405163293ceef960e21b815260040160405180910390fd5b600480546040516302265e3160e61b81528635606090811c9382018490526014880135901c916000916001600160a01b03909116906389978c4090602401602060405180830381865afa15801561067d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106a19190613efc565b9050826001600160a01b03168a6001600160a01b0316036106e9576001600160a01b038116156106e45760405163acd5a82360e01b815260040160405180910390fd5b61071b565b896001600160a01b0316816001600160a01b03161461071b5760405163acd5a82360e01b815260040160405180910390fd5b61072683838a61176a565b50505061080d565b7f000000000000000000000000000000000000000000000000000000000000000083036107f4578335606090811c90601486013560a090811c916020880135901c906034880135901c88158061079957506107956001600160601b03808316908516613f2f565b8914155b156107b75760405163c52e3eff60e01b815260040160405180910390fd5b6001600160601b038316156107d1576107d184838561176a565b6001600160601b038116156107eb576107eb82858361176a565b5050505061080d565b604051630d3dcde560e31b815260040160405180910390fd5b5050505050505050565b6004548390600160a01b900460ff16610843576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610892573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b69190613efc565b6001600160a01b0316146108dd5760405163acd5a82360e01b815260040160405180910390fd5b6108e884848461176a565b6040516323b872dd60e01b81523360048201523060248201526001600160601b03831660448201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906323b872dd906064016020604051808303816000875af1158015610963573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109879190613f42565b15156001146109a95760405163022e258160e11b815260040160405180910390fd5b50505050565b6004548390600160a01b900460ff166109db576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610a2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a4e9190613efc565b6001600160a01b031614610a755760405163acd5a82360e01b815260040160405180910390fd5b6109a9848484611b16565b600454600160a01b900460ff16610aaa576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015610af8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1c9190613efc565b6001600160a01b031614610b435760405163acd5a82360e01b815260040160405180910390fd5b610b4d3382612213565b50565b600454600160a01b900460ff16610b7a576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015610bc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bec9190613efc565b6001600160a01b031614610c135760405163acd5a82360e01b815260040160405180910390fd5b610b4d338261238f565b600080610c2e836101000135612513565b90506000610c4260c0850160a08601613f64565b66ffffffffffffff166038610c5d60a0870160808801613f8d565b62ffffff16901b6050610c766080880160608901613fb2565b63ffffffff16901b6070610c906060890160408a01613fd8565b65ffffffffffff16901b60a0610cac60408a0160208b01614000565b6001600160601b0316901b171717179050600063fcb7796f60e01b85600001600001358385604051602001610d0193929190928352602083019190915260601b6001600160601b031916604082015260540190565b60408051808303601f1901815282825280516020918201206001600160e01b0319949094168184015282820193909352805180830382018152606083018252805190840120600554601960f81b6080850152600160f81b6081850152608284015260a2808401919091528151808403909101815260c29092019052805191012095945050505050565b6000828152602081815260408083206001600160a01b038516845290915281205460ff16610db9576000610ddb565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b90505b92915050565b6004548290600160a01b900460ff16610e10576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610e5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e839190613efc565b6001600160a01b031614610eaa5760405163acd5a82360e01b815260040160405180910390fd5b610eb48383612213565b505050565b6004548290600160a01b900460ff16610ee5576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610f34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f589190613efc565b6001600160a01b031614610f7f5760405163acd5a82360e01b815260040160405180910390fd5b610eb4838361238f565b600454600160a01b900460ff16610fb3576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015611001573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110259190613efc565b6001600160a01b03161461104c5760405163acd5a82360e01b815260040160405180910390fd5b610b4d33826125d0565b604080518082018252600c81526b486f70724368616e6e656c7360a01b6020918201528151808301835260058152640322e302e360dc1b9082015281517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f918101919091527f84e6908f343601d9ce9fb60d8250394eb8a51c56f1876bc1e017c97acd6567f2918101919091527fb4bcb154e38601c389396fa918314da42d4626f13ef6d0ceb07e5f5d26b2fbc360608201524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090506005548114610b4d57600581905560405181907f771f5240ae5fd8a7640d3fb82fa70aab2fb1dbf35f2ef464f8509946717664c590600090a250565b604080516020808201869052833582840152838101356060830152610100850135608083015260c0808601803560a08086019190915260e0808901358487015286518087039094018452909401909452805191012060009260c89190911c916111da91908601613f64565b66ffffffffffffff90811691161115949350505050565b60608167ffffffffffffffff81111561120c5761120c61401b565b60405190808252806020026020018201604052801561123f57816020015b606081526020019060019003908161122a5790505b50905060005b828110156112df576112af3085858481811061126357611263614031565b90506020028101906112759190614047565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061272092505050565b8282815181106112c1576112c1614031565b602002602001018190525080806112d79061408e565b915050611245565b5092915050565b6004548290600160a01b900460ff16611312576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015611361573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113859190613efc565b6001600160a01b0316146113ac5760405163acd5a82360e01b815260040160405180910390fd5b610eb483836125d0565b6040516001600160601b0319606084811b8216602084015283901b16603482015260009060480160405160208183030381529060405280519060200120905092915050565b604080518082018252600a8152692437b8392632b233b2b960b11b6020918201528151808301835260058152640312e302e360dc1b9082015281517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f818301527f6cd681790c78c220517b099a737f8e85f69e797abe4e2910fb189b61db4bf2cd818401527f06c015bd22b4c69690933c1058878ebdfef31f9aaae40bbe86d8a09fe1b2972c60608201524660808201523060a0808301919091528351808303909101815260c090910190925281519101206003548114610b4d57600381905560405181907fa43fad83920fd09445855e854e73c9c532e17402c9ceb09993a2392843a5bdb990600090a250565b600454600160a01b900460ff16611533576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015611581573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115a59190613efc565b6001600160a01b0316146115cc5760405163acd5a82360e01b815260040160405180910390fd5b6115d733838361176a565b6040516323b872dd60e01b81523360048201523060248201526001600160601b03821660448201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906323b872dd906064016020604051808303816000875af1158015611652573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116769190613f42565b15156001146116985760405163022e258160e11b815260040160405180910390fd5b5050565b600454600160a01b900460ff166116c6576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015611714573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117389190613efc565b6001600160a01b03161461175f5760405163acd5a82360e01b815260040160405180910390fd5b611698338383611b16565b8060016001600160601b03821610156117965760405163c52e3eff60e01b815260040160405180910390fd5b6a084595161401484a0000006001600160601b03821611156117cb5760405163293ceef960e21b815260040160405180910390fd5b8383806001600160a01b0316826001600160a01b0316036117ff57604051634bd1d76960e11b815260040160405180910390fd5b6001600160a01b03821661185b5760405163eac0d38960e01b815260206004820152601860248201527f736f75726365206d757374206e6f7420626520656d707479000000000000000060448201526064015b60405180910390fd5b6001600160a01b0381166118b25760405163eac0d38960e01b815260206004820152601d60248201527f64657374696e6174696f6e206d757374206e6f7420626520656d7074790000006044820152606401611852565b60006118be87876113b6565b600081815260066020526040902090915060028154600160c81b900460ff1660028111156118ee576118ee613cbf565b0361194f5760405163499463c160e01b815260206004820152602a60248201527f63616e6e6f742066756e642061206368616e6e656c20746861742077696c6c2060448201526931b637b9b29039b7b7b760b11b6064820152608401611852565b80546119659087906001600160601b03166140a7565b81546001600160601b0319166001600160601b039190911617815560008154600160c81b900460ff16600281111561199f5761199f613cbf565b03611aaa5780546119bd90600160b01b900462ffffff1660016140c7565b815462ffffff91909116600160b01b026dff00000000000000ffffffffffff60601b19166dffffffff00000000ffffffffffff60601b1990911617600160c81b178155604080517fdd90f938230335e59dc925c57ecb0e27a28c2d87356e31f00cd5554abd6c1b2d602082015260608a811b6001600160601b03199081169383019390935289901b9091166054820152611a69906068015b604051602081830303815290604052612745565b866001600160a01b0316886001600160a01b03167fdd90f938230335e59dc925c57ecb0e27a28c2d87356e31f00cd5554abd6c1b2d60405160405180910390a35b8054604051611add91611a55916000805160206142c98339815191529186916001600160601b03909116906020016140e3565b80546040516001600160601b03909116815282906000805160206142c98339815191529060200160405180910390a25050505050505050565b611b266040830160208401614000565b60016001600160601b0382161015611b515760405163c52e3eff60e01b815260040160405180910390fd5b6a084595161401484a0000006001600160601b0382161115611b865760405163293ceef960e21b815260040160405180910390fd5b826101000135611b958161282b565b611bb257604051633ae4ed6b60e01b815260040160405180910390fd5b8335600090815260066020526040902060018154600160c81b900460ff166002811115611be157611be1613cbf565b14158015611c0c575060028154600160c81b900460ff166002811115611c0957611c09613cbf565b14155b15611c745760405163499463c160e01b815260206004820152603160248201527f7370656e64696e67206368616e6e656c206d757374206265204f50454e206f726044820152702050454e44494e475f544f5f434c4f534560781b6064820152608401611852565b611c8460a0860160808701613f8d565b8154600160b01b900462ffffff908116911614611ce45760405163499463c160e01b815260206004820152601860248201527f6368616e6e656c2065706f6368206d757374206d6174636800000000000000006044820152606401611852565b6000611cf66060870160408801613fd8565b90506000611d0a6080880160608901613fb2565b8354909150600160601b900465ffffffffffff16600163ffffffff83161080611d4257508065ffffffffffff168365ffffffffffff16105b15611d605760405163686e1e0f60e11b815260040160405180910390fd5b611d706040890160208a01614000565b84546001600160601b0391821691161015611d9e57604051632c51d8db60e21b815260040160405180910390fd5b6000611da989610c1d565b9050611db6818a8a61116f565b611dd35760405163ee835c8960e01b815260040160405180910390fd5b600060405180606001604052808381526020018c6001600160a01b03168152602001600554604051602001611e0a91815260200190565b60408051601f1981840301815291905290529050611e36611e30368b90038b018b614106565b8261284d565b611e53576040516312bfb7b760e31b815260040160405180910390fd5b6000611e688360c08d013560e08e0135612ad6565b90508a35611e76828e6113b6565b14611e94576040516366eea9ab60e11b815260040160405180910390fd5b611ea463ffffffff8616876141a4565b875465ffffffffffff91909116600160601b0265ffffffffffff60601b19909116178755611ed860408c0160208d01614000565b8754611eed91906001600160601b03166141c3565b87546001600160601b0319166001600160601b03919091169081178855604051611f4291611a55917f22e2a422a8860656a3a33cfa1daf771e76798ce5649747957235025de12e0b24918f35916020016140e3565b86546040516001600160601b0390911681528b35907f22e2a422a8860656a3a33cfa1daf771e76798ce5649747957235025de12e0b249060200160405180910390a26000611f908d836113b6565b9050600060066000838152602001908152602001600020905061201c7f7165e2ebc7ce35cc98cb7666f9945b3617f3f36326b76d18937ba5fecf18739a8e600001600001358b600001600c9054906101000a900465ffffffffffff16604051602001611a5593929190928352602083019190915260d01b6001600160d01b031916604082015260460190565b8854604051600160601b90910465ffffffffffff1681528d35907f7165e2ebc7ce35cc98cb7666f9945b3617f3f36326b76d18937ba5fecf18739a9060200160405180910390a260008154600160c81b900460ff16600281111561208257612082613cbf565b0361216c577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663a9059cbb338f60000160200160208101906120cd9190614000565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526001600160601b031660248201526044016020604051808303816000875af1158015612121573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121459190613f42565b15156001146121675760405163022e258160e11b815260040160405180910390fd5b612203565b61217c60408e0160208f01614000565b815461219191906001600160601b03166140a7565b81546001600160601b0319166001600160601b039190911690811782556040516121d391611a55916000805160206142c98339815191529186916020016140e3565b80546040516001600160601b03909116815282906000805160206142c98339815191529060200160405180910390a25b5050505050505050505050505050565b600061221f82846113b6565b60008181526006602052604081209192508154600160c81b900460ff16600281111561224d5761224d613cbf565b0361226b5760405163499463c160e01b8152600401611852906141e3565b8054600163ff00000160b01b031981168255604080516000805160206142e983398151915260208201529081018490526001600160601b03909116906122b390606001611a55565b60405183906000805160206142e983398151915290600090a280156123885760405163a9059cbb60e01b81526001600160a01b038581166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044015b6020604051808303816000875af1158015612342573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123669190613f42565b15156001146123885760405163022e258160e11b815260040160405180910390fd5b5050505050565b600061239b83836113b6565b600081815260066020526040902090915060028154600160c81b900460ff1660028111156123cb576123cb613cbf565b146124285760405163499463c160e01b815260206004820152602660248201527f6368616e6e656c207374617465206d7573742062652050454e44494e475f544f6044820152655f434c4f534560d01b6064820152608401611852565b805463ffffffff428116600160901b9092041610612459576040516338b2019560e11b815260040160405180910390fd5b8054600163ff00000160b01b031981168255604080516000805160206142e983398151915260208201529081018490526001600160601b03909116906124a190606001611a55565b60405183906000805160206142e983398151915290600090a280156123885760405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb90604401612323565b6000600181601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179870014551231950b75fc4402da1732fc9bebe197f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179887096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa1580156125bf573d6000803e3d6000fd5b5050604051601f1901519392505050565b60006125dc83836113b6565b60008181526006602052604081209192508154600160c81b900460ff16600281111561260a5761260a613cbf565b036126285760405163499463c160e01b8152600401611852906141e3565b6126527f000000000000000000000000000000000000000000000000000000000000000042614233565b8154600160c91b67ff000000ffffffff60901b1990911660ff60c81b19600160901b63ffffffff949094168402161717808355604080517f07b5c950597fc3bed92e2ad37fa84f701655acb372982e486f5fad3607f04a5c602082015290810185905291900460e01b6001600160e01b03191660608201526126d690606401611a55565b8054604051600160901b90910463ffffffff16815282907f07b5c950597fc3bed92e2ad37fa84f701655acb372982e486f5fad3607f04a5c9060200160405180910390a250505050565b6060610ddb83836040518060600160405280602781526020016142a260279139612afc565b600154600090612783907f000000000000000000000000000000000000000000000000000000000000000090600160e01b900463ffffffff16613f2f565b42111561278e575060015b60035460015483516020808601919091206040805180840195909552439085015291901b63ffffffff19166060830152608082015260a00160408051808303601f190181529190528051602091820120600180546001600160e01b0319169190921c1790558015611698576001805463ffffffff600160e01b808304821681026001600160e01b0390931692831760025542909116021790555050565b6000811580610dde57505070014551231950b75fc4402da1732fc9bebe191190565b60006401000003d019836060015110158061287257506401000003d019836040015110155b1561289057604051633ae4ed6b60e01b815260040160405180910390fd5b6128a283600001518460200151612b74565b6128bf57604051633922a54160e11b815260040160405180910390fd5b600080612911846020015185600001516040516020016128f892919060609290921b6001600160601b0319168252601482015260340190565b6040516020818303038152906040528560400151612b9f565b91509150600061292686604001518484612c25565b905061296186608001518760a00151604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b6001600160a01b0316816001600160a01b03161461299257604051631dbfb9b360e31b815260040160405180910390fd5b60006129ab876060015188600001518960200151612c25565b90506129e68760c001518860e00151604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b6001600160a01b0316816001600160a01b031614612a1757604051631dbfb9b360e31b815260040160405180910390fd5b600080612a4989608001518a60a001518b60c001518c60e001516401000003d019612a429190614250565b6000612cc4565b6020808b01518c518d8301518d51604051969850949650600095612ac195612aa8958a928a92910160609690961b6001600160601b03191686526014860194909452603485019290925260548401526074830152609482015260b40190565b6040516020818303038152906040528a60400151612e4b565b60608b01511497505050505050505092915050565b6000806000612ae6868686612ebc565b91509150612af381612ef5565b50949350505050565b6060600080856001600160a01b031685604051612b199190614263565b600060405180830381855af49150503d8060008114612b54576040519150601f19603f3d011682016040523d82523d6000602084013e612b59565b606091505b5091509150612b6a8683838761303f565b9695505050505050565b60006401000003d01980846401000003d019868709096007086401000003d019838409149392505050565b600080600080612baf86866130c0565b91509150600080612bbf8461317c565b91509150600080612bcf8561317c565b91509150600080612c03868686867f3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533612cc4565b91509150612c11828261343e565b9950995050505050505050505b9250929050565b600080612c3360028461427f565b600003612c425750601b612c46565b50601c5b60016000828670014551231950b75fc4402da1732fc9bebe19888a096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa158015612cb0573d6000803e3d6000fd5b5050604051601f1901519695505050505050565b600080838614198588141615612cd957600080fd5b600080858814878a141660018114612cf6578015612d7357612dee565b6401000003d019866401000003d0198b60020908915060405160208152602080820152602060408201528260608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa612d5357600080fd5b6401000003d01981516401000003d019808e8f0960030909935050612dee565b6401000003d0198a6401000003d019038908915060405160208152602080820152602060408201528260608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa612dce57600080fd5b6401000003d01981516401000003d0198c6401000003d019038b08099350505b50506401000003d01980896401000003d01903886401000003d01903086401000003d0198384090892506401000003d019876401000003d019036401000003d01980866401000003d019038c088409089150509550959350505050565b6000806000612e5a858561372b565b9150915060405160308152602080820152602060408201528260608201528160808201526001609082015270014551231950b75fc4402da1732fc9bebe1960b082015260208160d0836005600019fa612eb257600080fd5b5195945050505050565b6000806001600160ff1b03831681612ed960ff86901c601b613f2f565b9050612ee78782888561382b565b935093505050935093915050565b6000816004811115612f0957612f09613cbf565b03612f115750565b6001816004811115612f2557612f25613cbf565b03612f725760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606401611852565b6002816004811115612f8657612f86613cbf565b03612fd35760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401611852565b6003816004811115612fe757612fe7613cbf565b03610b4d5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401611852565b606083156130ae5782516000036130a7576001600160a01b0385163b6130a75760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401611852565b50816130b8565b6130b883836138ef565b949350505050565b60008060008060006130d28787613919565b9250925092506040516030815260208082015260206040820152836060820152826080820152600160908201526401000003d01960b082015260208160d0836005600019fa61312057600080fd5b80519550506040516030815260208082015282605082015260206040820152816070820152600160908201526401000003d01960b082015260208160d0836005600019fa61316d57600080fd5b80519450505050509250929050565b6000806401000003d0198384096401000003d019816401000003db190990506401000003d0198182096401000003d01982820890506401000003d019600182086401000003d0196106eb8209905060008215600181146131e15780156131ef576131fb565b6401000003db1991506131fb565b836401000003d0190391505b506401000003d019817f3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a4445330990506401000003d01982830992506401000003d0198182096401000003d019817f3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533096401000003d01981860894506401000003d01984860994506401000003d01983830991506401000003d019826106eb0990506401000003d0198186089450506401000003d01983860996506000806401000003d0198384096401000003d0198488096401000003d0198183099150604051602081526020808201526020604082015282606082015263400000f5600160fe1b0360808201526401000003d01960a082015260208160c0836005600019fa61332157600080fd5b6401000003d01982825109925050506401000003d0197f31fdf302724013e57ad13fb38f842afeec184f00a74789dd286729c8303c4a5982096401000003d0198283096401000003d0198682099050888114600181146133865780156133925761339a565b6001945083955061339a565b600094508295505b505050506401000003d0198a880997506401000003d019828909975080156133c3578498508197505b5050506002850660028806146133df57846401000003d0190394505b604051935060208452602080850152602060408501528060608501525050506401000003d21960808201526401000003d01960a082015260208160c0836005600019fa61342b57600080fd5b6401000003d01981518409925050915091565b6000806401000003d0198485096401000003d0198186096401000003d019807f8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c76401000003d019897f07d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c658109086401000003d01980857f534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262096401000003d019857f8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c0908086401000003d0197fd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b6401000003d019808a7fedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d140986080860405160208152602080820152602060408201528160608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa61359d57600080fd5b805191506401000003d01982840996506401000003d019807f4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c6401000003d0198c7fc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a309086401000003d01980887f29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931096401000003d019887f2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d8409080892506401000003d019806401000006c4196401000003d0198c7f7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c257309086401000003d01980887f6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f098708089450604051905060208152602080820152602060408201528460608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa61370d57600080fd5b5193506401000003d019905083818389090993505050509250929050565b60008060ff8351111561373d57600080fd5b60006040516088602060005b885181101561376a5788820151848401526020928301929182019101613749565b505060898751019050603081830153600201602060005b87518110156137a25787820151848401526020928301929182019101613781565b5050608b8651885101019050855181830153508551855101608c018120915050604051818152600160208201536021602060005b87518110156137f757878201518484015260209283019291820191016137d6565b5050508451855160210182015384516022018120935083821881526002602082015384516022018120925050509250929050565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561386257506000905060036138e6565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa1580156138b6573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166138df576000600192509250506138e6565b9150600090505b94509492505050565b8151156138ff5781518083602001fd5b8060405162461bcd60e51b81526004016118529190613e86565b600080600060ff8451111561392d57600080fd5b60006040516088602060005b895181101561395a5789820151848401526020928301929182019101613939565b505060898851019050606081830153600201602060005b88518110156139925788820151848401526020928301929182019101613971565b5050608b8751895101019050865181830153508651865101608c018120915050604051818152600160208201536021602060005b88518110156139e757888201518484015260209283019291820191016139c6565b5050508551865160210182015385516022018120945084821881526002602082015385516022018120935083821881526003602082015385516022018120925050509250925092565b6001600160a01b0381168114610b4d57600080fd5b60008083601f840112613a5757600080fd5b50813567ffffffffffffffff811115613a6f57600080fd5b602083019150836020828501011115612c1e57600080fd5b60008060008060008060008060c0898b031215613aa357600080fd5b8835613aae81613a30565b97506020890135613abe81613a30565b96506040890135613ace81613a30565b955060608901359450608089013567ffffffffffffffff80821115613af257600080fd5b613afe8c838d01613a45565b909650945060a08b0135915080821115613b1757600080fd5b50613b248b828c01613a45565b999c989b5096995094979396929594505050565b80356001600160601b0381168114613b4f57600080fd5b919050565b600080600060608486031215613b6957600080fd5b8335613b7481613a30565b92506020840135613b8481613a30565b9150613b9260408501613b38565b90509250925092565b60006101208284031215613bae57600080fd5b50919050565b60006101008284031215613bae57600080fd5b60008060006102408486031215613bdd57600080fd5b8335613be881613a30565b9250613bf78560208601613b9b565b9150613b92856101408601613bb4565b600060208284031215613c1957600080fd5b8135613c2481613a30565b9392505050565b60006101208284031215613c3e57600080fd5b610ddb8383613b9b565b60008060408385031215613c5b57600080fd5b823591506020830135613c6d81613a30565b809150509250929050565b60008060408385031215613c8b57600080fd5b8235613c9681613a30565b91506020830135613c6d81613a30565b600060208284031215613cb857600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b6001600160601b038616815265ffffffffffff8516602082015263ffffffff8416604082015262ffffff8316606082015260a0810160038310613d2857634e487b7160e01b600052602160045260246000fd5b8260808301529695505050505050565b60008060006102408486031215613d4e57600080fd5b83359250613bf78560208601613b9b565b60008060208385031215613d7257600080fd5b823567ffffffffffffffff80821115613d8a57600080fd5b818501915085601f830112613d9e57600080fd5b813581811115613dad57600080fd5b8660208260051b8501011115613dc257600080fd5b60209290920196919550909350505050565b60005b83811015613def578181015183820152602001613dd7565b50506000910152565b60008151808452613e10816020860160208601613dd4565b601f01601f19169290920160200192915050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015613e7957603f19888603018452613e67858351613df8565b94509285019290850190600101613e4b565b5092979650505050505050565b602081526000610ddb6020830184613df8565b60008060408385031215613eac57600080fd5b8235613eb781613a30565b9150613ec560208401613b38565b90509250929050565b6000806102208385031215613ee257600080fd5b613eec8484613b9b565b9150613ec5846101208501613bb4565b600060208284031215613f0e57600080fd5b8151613c2481613a30565b634e487b7160e01b600052601160045260246000fd5b80820180821115610dde57610dde613f19565b600060208284031215613f5457600080fd5b81518015158114613c2457600080fd5b600060208284031215613f7657600080fd5b813566ffffffffffffff81168114613c2457600080fd5b600060208284031215613f9f57600080fd5b813562ffffff81168114613c2457600080fd5b600060208284031215613fc457600080fd5b813563ffffffff81168114613c2457600080fd5b600060208284031215613fea57600080fd5b813565ffffffffffff81168114613c2457600080fd5b60006020828403121561401257600080fd5b610ddb82613b38565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000808335601e1984360301811261405e57600080fd5b83018035915067ffffffffffffffff82111561407957600080fd5b602001915036819003821315612c1e57600080fd5b6000600182016140a0576140a0613f19565b5060010190565b6001600160601b038181168382160190808211156112df576112df613f19565b62ffffff8181168382160190808211156112df576112df613f19565b928352602083019190915260a01b6001600160a01b0319166040820152604c0190565b600061010080838503121561411a57600080fd5b6040519081019067ffffffffffffffff8211818310171561414b57634e487b7160e01b600052604160045260246000fd5b81604052833581526020840135602082015260408401356040820152606084013560608201526080840135608082015260a084013560a082015260c084013560c082015260e084013560e0820152809250505092915050565b65ffffffffffff8181168382160190808211156112df576112df613f19565b6001600160601b038281168282160390808211156112df576112df613f19565b60208082526030908201527f6368616e6e656c206d7573742068617665207374617465204f50454e206f722060408201526f50454e44494e475f544f5f434c4f534560801b606082015260800190565b63ffffffff8181168382160190808211156112df576112df613f19565b81810381811115610dde57610dde613f19565b60008251614275818460208701613dd4565b9190910192915050565b60008261429c57634e487b7160e01b600052601260045260246000fd5b50069056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65645fa17246d3a5d68d42baa94cde33042180b783a399c02bf63ac2076e0f708738ceeab2eef998c17fe96f30f83fbf3c55fc5047f6e40c55a0cf72d236e9d2ba72a2646970667358221220657b679d135fc011c665beb7b96a5c08d8e786ec0e285b7486f2829d5e9e2ade64736f6c634300081300338b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c1000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000004f7c7de3ba2b29ed8b2448df2213ca43f94e45c0

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101e45760003560e01c80637c8e28da1161010f578063c966c4fe116100a2578063fc0c546a11610071578063fc0c546a146104d2578063fc55309a14610511578063fcb7796f14610524578063ffa1ad741461053757600080fd5b8063c966c4fe14610487578063dc96fd5014610490578063ddad190214610498578063f698da25146104c957600080fd5b8063ac9650d8116100de578063ac9650d81461043b578063b920deed1461045b578063bda65f4514610461578063be9babdc1461047457600080fd5b80637c8e28da146103c157806387352d65146103d457806389ccfe89146104105780638c3710c91461041857600080fd5b806329392e3211610187578063651514bf11610156578063651514bf146102ef57806372581cc01461030257806378d8016d146103295780637a7ebd7b1461035057600080fd5b806329392e321461028357806344dae6f8146102a357806354a2edf5146102ca5780635d2f07c5146102dd57600080fd5b80631a7ffe7a116101c35780631a7ffe7a1461022457806323cb3ac01461023757806324086cc21461024a578063249cb3fa1461027057600080fd5b806223de29146101e95780630abec58f146101fe5780630cd88d7214610211575b600080fd5b6101fc6101f7366004613a87565b61055b565b005b6101fc61020c366004613b54565b610817565b6101fc61021f366004613bc7565b6109af565b6101fc610232366004613c07565b610a80565b6101fc610245366004613c07565b610b50565b61025d610258366004613c2b565b610c1d565b6040519081526020015b60405180910390f35b61025d61027e366004613c48565b610d8a565b61028b600181565b6040516001600160601b039091168152602001610267565b61025d7f000000000000000000000000000000000000000000000000000000000000002881565b6101fc6102d8366004613c78565b610de4565b61028b6a084595161401484a00000081565b6101fc6102fd366004613c78565b610eb9565b61025d7fb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b81565b61025d7f000000000000000000000000000000000000000000000000000000000000004081565b6103b061035e366004613ca6565b6006602052600090815260409020546001600160601b03811690600160601b810465ffffffffffff1690600160901b810463ffffffff1690600160b01b810462ffffff1690600160c81b900460ff1685565b604051610267959493929190613cd5565b6101fc6103cf366004613c07565b610f89565b6103fb7f000000000000000000000000000000000000000000000000000000000000012c81565b60405163ffffffff9091168152602001610267565b6101fc611056565b61042b610426366004613d38565b61116f565b6040519015158152602001610267565b61044e610449366004613d5f565b6111f1565b6040516102679190613e24565b426103fb565b6101fc61046f366004613c78565b6112e6565b61025d610482366004613c78565b6113b6565b61025d60035481565b6101fc6113fb565b6104bc604051806040016040528060058152602001640312e302e360dc1b81525081565b6040516102679190613e86565b61025d60055481565b6104f97f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c181565b6040516001600160a01b039091168152602001610267565b6101fc61051f366004613e99565b611509565b6101fc610532366004613ece565b61169c565b6104bc604051806040016040528060058152602001640322e302e360dc1b81525081565b336001600160a01b037f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c116146105a457604051635079ff7560e11b815260040160405180910390fd5b6001600160a01b03861630146105cd57604051631738922160e31b815260040160405180910390fd5b821561080d577f0000000000000000000000000000000000000000000000000000000000000028830361072e576001600160601b038511156106225760405163293ceef960e21b815260040160405180910390fd5b600480546040516302265e3160e61b81528635606090811c9382018490526014880135901c916000916001600160a01b03909116906389978c4090602401602060405180830381865afa15801561067d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106a19190613efc565b9050826001600160a01b03168a6001600160a01b0316036106e9576001600160a01b038116156106e45760405163acd5a82360e01b815260040160405180910390fd5b61071b565b896001600160a01b0316816001600160a01b03161461071b5760405163acd5a82360e01b815260040160405180910390fd5b61072683838a61176a565b50505061080d565b7f000000000000000000000000000000000000000000000000000000000000004083036107f4578335606090811c90601486013560a090811c916020880135901c906034880135901c88158061079957506107956001600160601b03808316908516613f2f565b8914155b156107b75760405163c52e3eff60e01b815260040160405180910390fd5b6001600160601b038316156107d1576107d184838561176a565b6001600160601b038116156107eb576107eb82858361176a565b5050505061080d565b604051630d3dcde560e31b815260040160405180910390fd5b5050505050505050565b6004548390600160a01b900460ff16610843576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610892573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108b69190613efc565b6001600160a01b0316146108dd5760405163acd5a82360e01b815260040160405180910390fd5b6108e884848461176a565b6040516323b872dd60e01b81523360048201523060248201526001600160601b03831660448201527f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c16001600160a01b0316906323b872dd906064016020604051808303816000875af1158015610963573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109879190613f42565b15156001146109a95760405163022e258160e11b815260040160405180910390fd5b50505050565b6004548390600160a01b900460ff166109db576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610a2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a4e9190613efc565b6001600160a01b031614610a755760405163acd5a82360e01b815260040160405180910390fd5b6109a9848484611b16565b600454600160a01b900460ff16610aaa576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015610af8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1c9190613efc565b6001600160a01b031614610b435760405163acd5a82360e01b815260040160405180910390fd5b610b4d3382612213565b50565b600454600160a01b900460ff16610b7a576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015610bc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bec9190613efc565b6001600160a01b031614610c135760405163acd5a82360e01b815260040160405180910390fd5b610b4d338261238f565b600080610c2e836101000135612513565b90506000610c4260c0850160a08601613f64565b66ffffffffffffff166038610c5d60a0870160808801613f8d565b62ffffff16901b6050610c766080880160608901613fb2565b63ffffffff16901b6070610c906060890160408a01613fd8565b65ffffffffffff16901b60a0610cac60408a0160208b01614000565b6001600160601b0316901b171717179050600063fcb7796f60e01b85600001600001358385604051602001610d0193929190928352602083019190915260601b6001600160601b031916604082015260540190565b60408051808303601f1901815282825280516020918201206001600160e01b0319949094168184015282820193909352805180830382018152606083018252805190840120600554601960f81b6080850152600160f81b6081850152608284015260a2808401919091528151808403909101815260c29092019052805191012095945050505050565b6000828152602081815260408083206001600160a01b038516845290915281205460ff16610db9576000610ddb565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b90505b92915050565b6004548290600160a01b900460ff16610e10576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610e5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e839190613efc565b6001600160a01b031614610eaa5760405163acd5a82360e01b815260040160405180910390fd5b610eb48383612213565b505050565b6004548290600160a01b900460ff16610ee5576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015610f34573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f589190613efc565b6001600160a01b031614610f7f5760405163acd5a82360e01b815260040160405180910390fd5b610eb4838361238f565b600454600160a01b900460ff16610fb3576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015611001573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110259190613efc565b6001600160a01b03161461104c5760405163acd5a82360e01b815260040160405180910390fd5b610b4d33826125d0565b604080518082018252600c81526b486f70724368616e6e656c7360a01b6020918201528151808301835260058152640322e302e360dc1b9082015281517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f918101919091527f84e6908f343601d9ce9fb60d8250394eb8a51c56f1876bc1e017c97acd6567f2918101919091527fb4bcb154e38601c389396fa918314da42d4626f13ef6d0ceb07e5f5d26b2fbc360608201524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090506005548114610b4d57600581905560405181907f771f5240ae5fd8a7640d3fb82fa70aab2fb1dbf35f2ef464f8509946717664c590600090a250565b604080516020808201869052833582840152838101356060830152610100850135608083015260c0808601803560a08086019190915260e0808901358487015286518087039094018452909401909452805191012060009260c89190911c916111da91908601613f64565b66ffffffffffffff90811691161115949350505050565b60608167ffffffffffffffff81111561120c5761120c61401b565b60405190808252806020026020018201604052801561123f57816020015b606081526020019060019003908161122a5790505b50905060005b828110156112df576112af3085858481811061126357611263614031565b90506020028101906112759190614047565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061272092505050565b8282815181106112c1576112c1614031565b602002602001018190525080806112d79061408e565b915050611245565b5092915050565b6004548290600160a01b900460ff16611312576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b81526001600160a01b03848116938201939093523392909116906389978c4090602401602060405180830381865afa158015611361573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113859190613efc565b6001600160a01b0316146113ac5760405163acd5a82360e01b815260040160405180910390fd5b610eb483836125d0565b6040516001600160601b0319606084811b8216602084015283901b16603482015260009060480160405160208183030381529060405280519060200120905092915050565b604080518082018252600a8152692437b8392632b233b2b960b11b6020918201528151808301835260058152640312e302e360dc1b9082015281517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f818301527f6cd681790c78c220517b099a737f8e85f69e797abe4e2910fb189b61db4bf2cd818401527f06c015bd22b4c69690933c1058878ebdfef31f9aaae40bbe86d8a09fe1b2972c60608201524660808201523060a0808301919091528351808303909101815260c090910190925281519101206003548114610b4d57600381905560405181907fa43fad83920fd09445855e854e73c9c532e17402c9ceb09993a2392843a5bdb990600090a250565b600454600160a01b900460ff16611533576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015611581573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115a59190613efc565b6001600160a01b0316146115cc5760405163acd5a82360e01b815260040160405180910390fd5b6115d733838361176a565b6040516323b872dd60e01b81523360048201523060248201526001600160601b03821660448201527f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c16001600160a01b0316906323b872dd906064016020604051808303816000875af1158015611652573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116769190613f42565b15156001146116985760405163022e258160e11b815260040160405180910390fd5b5050565b600454600160a01b900460ff166116c6576040516308a9441960e31b815260040160405180910390fd5b600480546040516302265e3160e61b815233928101929092526000916001600160a01b03909116906389978c4090602401602060405180830381865afa158015611714573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117389190613efc565b6001600160a01b03161461175f5760405163acd5a82360e01b815260040160405180910390fd5b611698338383611b16565b8060016001600160601b03821610156117965760405163c52e3eff60e01b815260040160405180910390fd5b6a084595161401484a0000006001600160601b03821611156117cb5760405163293ceef960e21b815260040160405180910390fd5b8383806001600160a01b0316826001600160a01b0316036117ff57604051634bd1d76960e11b815260040160405180910390fd5b6001600160a01b03821661185b5760405163eac0d38960e01b815260206004820152601860248201527f736f75726365206d757374206e6f7420626520656d707479000000000000000060448201526064015b60405180910390fd5b6001600160a01b0381166118b25760405163eac0d38960e01b815260206004820152601d60248201527f64657374696e6174696f6e206d757374206e6f7420626520656d7074790000006044820152606401611852565b60006118be87876113b6565b600081815260066020526040902090915060028154600160c81b900460ff1660028111156118ee576118ee613cbf565b0361194f5760405163499463c160e01b815260206004820152602a60248201527f63616e6e6f742066756e642061206368616e6e656c20746861742077696c6c2060448201526931b637b9b29039b7b7b760b11b6064820152608401611852565b80546119659087906001600160601b03166140a7565b81546001600160601b0319166001600160601b039190911617815560008154600160c81b900460ff16600281111561199f5761199f613cbf565b03611aaa5780546119bd90600160b01b900462ffffff1660016140c7565b815462ffffff91909116600160b01b026dff00000000000000ffffffffffff60601b19166dffffffff00000000ffffffffffff60601b1990911617600160c81b178155604080517fdd90f938230335e59dc925c57ecb0e27a28c2d87356e31f00cd5554abd6c1b2d602082015260608a811b6001600160601b03199081169383019390935289901b9091166054820152611a69906068015b604051602081830303815290604052612745565b866001600160a01b0316886001600160a01b03167fdd90f938230335e59dc925c57ecb0e27a28c2d87356e31f00cd5554abd6c1b2d60405160405180910390a35b8054604051611add91611a55916000805160206142c98339815191529186916001600160601b03909116906020016140e3565b80546040516001600160601b03909116815282906000805160206142c98339815191529060200160405180910390a25050505050505050565b611b266040830160208401614000565b60016001600160601b0382161015611b515760405163c52e3eff60e01b815260040160405180910390fd5b6a084595161401484a0000006001600160601b0382161115611b865760405163293ceef960e21b815260040160405180910390fd5b826101000135611b958161282b565b611bb257604051633ae4ed6b60e01b815260040160405180910390fd5b8335600090815260066020526040902060018154600160c81b900460ff166002811115611be157611be1613cbf565b14158015611c0c575060028154600160c81b900460ff166002811115611c0957611c09613cbf565b14155b15611c745760405163499463c160e01b815260206004820152603160248201527f7370656e64696e67206368616e6e656c206d757374206265204f50454e206f726044820152702050454e44494e475f544f5f434c4f534560781b6064820152608401611852565b611c8460a0860160808701613f8d565b8154600160b01b900462ffffff908116911614611ce45760405163499463c160e01b815260206004820152601860248201527f6368616e6e656c2065706f6368206d757374206d6174636800000000000000006044820152606401611852565b6000611cf66060870160408801613fd8565b90506000611d0a6080880160608901613fb2565b8354909150600160601b900465ffffffffffff16600163ffffffff83161080611d4257508065ffffffffffff168365ffffffffffff16105b15611d605760405163686e1e0f60e11b815260040160405180910390fd5b611d706040890160208a01614000565b84546001600160601b0391821691161015611d9e57604051632c51d8db60e21b815260040160405180910390fd5b6000611da989610c1d565b9050611db6818a8a61116f565b611dd35760405163ee835c8960e01b815260040160405180910390fd5b600060405180606001604052808381526020018c6001600160a01b03168152602001600554604051602001611e0a91815260200190565b60408051601f1981840301815291905290529050611e36611e30368b90038b018b614106565b8261284d565b611e53576040516312bfb7b760e31b815260040160405180910390fd5b6000611e688360c08d013560e08e0135612ad6565b90508a35611e76828e6113b6565b14611e94576040516366eea9ab60e11b815260040160405180910390fd5b611ea463ffffffff8616876141a4565b875465ffffffffffff91909116600160601b0265ffffffffffff60601b19909116178755611ed860408c0160208d01614000565b8754611eed91906001600160601b03166141c3565b87546001600160601b0319166001600160601b03919091169081178855604051611f4291611a55917f22e2a422a8860656a3a33cfa1daf771e76798ce5649747957235025de12e0b24918f35916020016140e3565b86546040516001600160601b0390911681528b35907f22e2a422a8860656a3a33cfa1daf771e76798ce5649747957235025de12e0b249060200160405180910390a26000611f908d836113b6565b9050600060066000838152602001908152602001600020905061201c7f7165e2ebc7ce35cc98cb7666f9945b3617f3f36326b76d18937ba5fecf18739a8e600001600001358b600001600c9054906101000a900465ffffffffffff16604051602001611a5593929190928352602083019190915260d01b6001600160d01b031916604082015260460190565b8854604051600160601b90910465ffffffffffff1681528d35907f7165e2ebc7ce35cc98cb7666f9945b3617f3f36326b76d18937ba5fecf18739a9060200160405180910390a260008154600160c81b900460ff16600281111561208257612082613cbf565b0361216c577f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c16001600160a01b031663a9059cbb338f60000160200160208101906120cd9190614000565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526001600160601b031660248201526044016020604051808303816000875af1158015612121573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121459190613f42565b15156001146121675760405163022e258160e11b815260040160405180910390fd5b612203565b61217c60408e0160208f01614000565b815461219191906001600160601b03166140a7565b81546001600160601b0319166001600160601b039190911690811782556040516121d391611a55916000805160206142c98339815191529186916020016140e3565b80546040516001600160601b03909116815282906000805160206142c98339815191529060200160405180910390a25b5050505050505050505050505050565b600061221f82846113b6565b60008181526006602052604081209192508154600160c81b900460ff16600281111561224d5761224d613cbf565b0361226b5760405163499463c160e01b8152600401611852906141e3565b8054600163ff00000160b01b031981168255604080516000805160206142e983398151915260208201529081018490526001600160601b03909116906122b390606001611a55565b60405183906000805160206142e983398151915290600090a280156123885760405163a9059cbb60e01b81526001600160a01b038581166004830152602482018390527f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c1169063a9059cbb906044015b6020604051808303816000875af1158015612342573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123669190613f42565b15156001146123885760405163022e258160e11b815260040160405180910390fd5b5050505050565b600061239b83836113b6565b600081815260066020526040902090915060028154600160c81b900460ff1660028111156123cb576123cb613cbf565b146124285760405163499463c160e01b815260206004820152602660248201527f6368616e6e656c207374617465206d7573742062652050454e44494e475f544f6044820152655f434c4f534560d01b6064820152608401611852565b805463ffffffff428116600160901b9092041610612459576040516338b2019560e11b815260040160405180910390fd5b8054600163ff00000160b01b031981168255604080516000805160206142e983398151915260208201529081018490526001600160601b03909116906124a190606001611a55565b60405183906000805160206142e983398151915290600090a280156123885760405163a9059cbb60e01b8152336004820152602481018290527f000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c16001600160a01b03169063a9059cbb90604401612323565b6000600181601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179870014551231950b75fc4402da1732fc9bebe197f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179887096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa1580156125bf573d6000803e3d6000fd5b5050604051601f1901519392505050565b60006125dc83836113b6565b60008181526006602052604081209192508154600160c81b900460ff16600281111561260a5761260a613cbf565b036126285760405163499463c160e01b8152600401611852906141e3565b6126527f000000000000000000000000000000000000000000000000000000000000012c42614233565b8154600160c91b67ff000000ffffffff60901b1990911660ff60c81b19600160901b63ffffffff949094168402161717808355604080517f07b5c950597fc3bed92e2ad37fa84f701655acb372982e486f5fad3607f04a5c602082015290810185905291900460e01b6001600160e01b03191660608201526126d690606401611a55565b8054604051600160901b90910463ffffffff16815282907f07b5c950597fc3bed92e2ad37fa84f701655acb372982e486f5fad3607f04a5c9060200160405180910390a250505050565b6060610ddb83836040518060600160405280602781526020016142a260279139612afc565b600154600090612783907f000000000000000000000000000000000000000000000000000000000036ee8090600160e01b900463ffffffff16613f2f565b42111561278e575060015b60035460015483516020808601919091206040805180840195909552439085015291901b63ffffffff19166060830152608082015260a00160408051808303601f190181529190528051602091820120600180546001600160e01b0319169190921c1790558015611698576001805463ffffffff600160e01b808304821681026001600160e01b0390931692831760025542909116021790555050565b6000811580610dde57505070014551231950b75fc4402da1732fc9bebe191190565b60006401000003d019836060015110158061287257506401000003d019836040015110155b1561289057604051633ae4ed6b60e01b815260040160405180910390fd5b6128a283600001518460200151612b74565b6128bf57604051633922a54160e11b815260040160405180910390fd5b600080612911846020015185600001516040516020016128f892919060609290921b6001600160601b0319168252601482015260340190565b6040516020818303038152906040528560400151612b9f565b91509150600061292686604001518484612c25565b905061296186608001518760a00151604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b6001600160a01b0316816001600160a01b03161461299257604051631dbfb9b360e31b815260040160405180910390fd5b60006129ab876060015188600001518960200151612c25565b90506129e68760c001518860e00151604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b6001600160a01b0316816001600160a01b031614612a1757604051631dbfb9b360e31b815260040160405180910390fd5b600080612a4989608001518a60a001518b60c001518c60e001516401000003d019612a429190614250565b6000612cc4565b6020808b01518c518d8301518d51604051969850949650600095612ac195612aa8958a928a92910160609690961b6001600160601b03191686526014860194909452603485019290925260548401526074830152609482015260b40190565b6040516020818303038152906040528a60400151612e4b565b60608b01511497505050505050505092915050565b6000806000612ae6868686612ebc565b91509150612af381612ef5565b50949350505050565b6060600080856001600160a01b031685604051612b199190614263565b600060405180830381855af49150503d8060008114612b54576040519150601f19603f3d011682016040523d82523d6000602084013e612b59565b606091505b5091509150612b6a8683838761303f565b9695505050505050565b60006401000003d01980846401000003d019868709096007086401000003d019838409149392505050565b600080600080612baf86866130c0565b91509150600080612bbf8461317c565b91509150600080612bcf8561317c565b91509150600080612c03868686867f3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533612cc4565b91509150612c11828261343e565b9950995050505050505050505b9250929050565b600080612c3360028461427f565b600003612c425750601b612c46565b50601c5b60016000828670014551231950b75fc4402da1732fc9bebe19888a096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa158015612cb0573d6000803e3d6000fd5b5050604051601f1901519695505050505050565b600080838614198588141615612cd957600080fd5b600080858814878a141660018114612cf6578015612d7357612dee565b6401000003d019866401000003d0198b60020908915060405160208152602080820152602060408201528260608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa612d5357600080fd5b6401000003d01981516401000003d019808e8f0960030909935050612dee565b6401000003d0198a6401000003d019038908915060405160208152602080820152602060408201528260608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa612dce57600080fd5b6401000003d01981516401000003d0198c6401000003d019038b08099350505b50506401000003d01980896401000003d01903886401000003d01903086401000003d0198384090892506401000003d019876401000003d019036401000003d01980866401000003d019038c088409089150509550959350505050565b6000806000612e5a858561372b565b9150915060405160308152602080820152602060408201528260608201528160808201526001609082015270014551231950b75fc4402da1732fc9bebe1960b082015260208160d0836005600019fa612eb257600080fd5b5195945050505050565b6000806001600160ff1b03831681612ed960ff86901c601b613f2f565b9050612ee78782888561382b565b935093505050935093915050565b6000816004811115612f0957612f09613cbf565b03612f115750565b6001816004811115612f2557612f25613cbf565b03612f725760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606401611852565b6002816004811115612f8657612f86613cbf565b03612fd35760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401611852565b6003816004811115612fe757612fe7613cbf565b03610b4d5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401611852565b606083156130ae5782516000036130a7576001600160a01b0385163b6130a75760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401611852565b50816130b8565b6130b883836138ef565b949350505050565b60008060008060006130d28787613919565b9250925092506040516030815260208082015260206040820152836060820152826080820152600160908201526401000003d01960b082015260208160d0836005600019fa61312057600080fd5b80519550506040516030815260208082015282605082015260206040820152816070820152600160908201526401000003d01960b082015260208160d0836005600019fa61316d57600080fd5b80519450505050509250929050565b6000806401000003d0198384096401000003d019816401000003db190990506401000003d0198182096401000003d01982820890506401000003d019600182086401000003d0196106eb8209905060008215600181146131e15780156131ef576131fb565b6401000003db1991506131fb565b836401000003d0190391505b506401000003d019817f3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a4445330990506401000003d01982830992506401000003d0198182096401000003d019817f3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533096401000003d01981860894506401000003d01984860994506401000003d01983830991506401000003d019826106eb0990506401000003d0198186089450506401000003d01983860996506000806401000003d0198384096401000003d0198488096401000003d0198183099150604051602081526020808201526020604082015282606082015263400000f5600160fe1b0360808201526401000003d01960a082015260208160c0836005600019fa61332157600080fd5b6401000003d01982825109925050506401000003d0197f31fdf302724013e57ad13fb38f842afeec184f00a74789dd286729c8303c4a5982096401000003d0198283096401000003d0198682099050888114600181146133865780156133925761339a565b6001945083955061339a565b600094508295505b505050506401000003d0198a880997506401000003d019828909975080156133c3578498508197505b5050506002850660028806146133df57846401000003d0190394505b604051935060208452602080850152602060408501528060608501525050506401000003d21960808201526401000003d01960a082015260208160c0836005600019fa61342b57600080fd5b6401000003d01981518409925050915091565b6000806401000003d0198485096401000003d0198186096401000003d019807f8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c76401000003d019897f07d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c658109086401000003d01980857f534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262096401000003d019857f8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c0908086401000003d0197fd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b6401000003d019808a7fedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d140986080860405160208152602080820152602060408201528160608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa61359d57600080fd5b805191506401000003d01982840996506401000003d019807f4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c6401000003d0198c7fc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a309086401000003d01980887f29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931096401000003d019887f2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d8409080892506401000003d019806401000006c4196401000003d0198c7f7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c257309086401000003d01980887f6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f098708089450604051905060208152602080820152602060408201528460608201526401000003d21960808201526401000003d01960a082015260208160c0836005600019fa61370d57600080fd5b5193506401000003d019905083818389090993505050509250929050565b60008060ff8351111561373d57600080fd5b60006040516088602060005b885181101561376a5788820151848401526020928301929182019101613749565b505060898751019050603081830153600201602060005b87518110156137a25787820151848401526020928301929182019101613781565b5050608b8651885101019050855181830153508551855101608c018120915050604051818152600160208201536021602060005b87518110156137f757878201518484015260209283019291820191016137d6565b5050508451855160210182015384516022018120935083821881526002602082015384516022018120925050509250929050565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561386257506000905060036138e6565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa1580156138b6573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166138df576000600192509250506138e6565b9150600090505b94509492505050565b8151156138ff5781518083602001fd5b8060405162461bcd60e51b81526004016118529190613e86565b600080600060ff8451111561392d57600080fd5b60006040516088602060005b895181101561395a5789820151848401526020928301929182019101613939565b505060898851019050606081830153600201602060005b88518110156139925788820151848401526020928301929182019101613971565b5050608b8751895101019050865181830153508651865101608c018120915050604051818152600160208201536021602060005b88518110156139e757888201518484015260209283019291820191016139c6565b5050508551865160210182015385516022018120945084821881526002602082015385516022018120935083821881526003602082015385516022018120925050509250925092565b6001600160a01b0381168114610b4d57600080fd5b60008083601f840112613a5757600080fd5b50813567ffffffffffffffff811115613a6f57600080fd5b602083019150836020828501011115612c1e57600080fd5b60008060008060008060008060c0898b031215613aa357600080fd5b8835613aae81613a30565b97506020890135613abe81613a30565b96506040890135613ace81613a30565b955060608901359450608089013567ffffffffffffffff80821115613af257600080fd5b613afe8c838d01613a45565b909650945060a08b0135915080821115613b1757600080fd5b50613b248b828c01613a45565b999c989b5096995094979396929594505050565b80356001600160601b0381168114613b4f57600080fd5b919050565b600080600060608486031215613b6957600080fd5b8335613b7481613a30565b92506020840135613b8481613a30565b9150613b9260408501613b38565b90509250925092565b60006101208284031215613bae57600080fd5b50919050565b60006101008284031215613bae57600080fd5b60008060006102408486031215613bdd57600080fd5b8335613be881613a30565b9250613bf78560208601613b9b565b9150613b92856101408601613bb4565b600060208284031215613c1957600080fd5b8135613c2481613a30565b9392505050565b60006101208284031215613c3e57600080fd5b610ddb8383613b9b565b60008060408385031215613c5b57600080fd5b823591506020830135613c6d81613a30565b809150509250929050565b60008060408385031215613c8b57600080fd5b8235613c9681613a30565b91506020830135613c6d81613a30565b600060208284031215613cb857600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b6001600160601b038616815265ffffffffffff8516602082015263ffffffff8416604082015262ffffff8316606082015260a0810160038310613d2857634e487b7160e01b600052602160045260246000fd5b8260808301529695505050505050565b60008060006102408486031215613d4e57600080fd5b83359250613bf78560208601613b9b565b60008060208385031215613d7257600080fd5b823567ffffffffffffffff80821115613d8a57600080fd5b818501915085601f830112613d9e57600080fd5b813581811115613dad57600080fd5b8660208260051b8501011115613dc257600080fd5b60209290920196919550909350505050565b60005b83811015613def578181015183820152602001613dd7565b50506000910152565b60008151808452613e10816020860160208601613dd4565b601f01601f19169290920160200192915050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015613e7957603f19888603018452613e67858351613df8565b94509285019290850190600101613e4b565b5092979650505050505050565b602081526000610ddb6020830184613df8565b60008060408385031215613eac57600080fd5b8235613eb781613a30565b9150613ec560208401613b38565b90509250929050565b6000806102208385031215613ee257600080fd5b613eec8484613b9b565b9150613ec5846101208501613bb4565b600060208284031215613f0e57600080fd5b8151613c2481613a30565b634e487b7160e01b600052601160045260246000fd5b80820180821115610dde57610dde613f19565b600060208284031215613f5457600080fd5b81518015158114613c2457600080fd5b600060208284031215613f7657600080fd5b813566ffffffffffffff81168114613c2457600080fd5b600060208284031215613f9f57600080fd5b813562ffffff81168114613c2457600080fd5b600060208284031215613fc457600080fd5b813563ffffffff81168114613c2457600080fd5b600060208284031215613fea57600080fd5b813565ffffffffffff81168114613c2457600080fd5b60006020828403121561401257600080fd5b610ddb82613b38565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000808335601e1984360301811261405e57600080fd5b83018035915067ffffffffffffffff82111561407957600080fd5b602001915036819003821315612c1e57600080fd5b6000600182016140a0576140a0613f19565b5060010190565b6001600160601b038181168382160190808211156112df576112df613f19565b62ffffff8181168382160190808211156112df576112df613f19565b928352602083019190915260a01b6001600160a01b0319166040820152604c0190565b600061010080838503121561411a57600080fd5b6040519081019067ffffffffffffffff8211818310171561414b57634e487b7160e01b600052604160045260246000fd5b81604052833581526020840135602082015260408401356040820152606084013560608201526080840135608082015260a084013560a082015260c084013560c082015260e084013560e0820152809250505092915050565b65ffffffffffff8181168382160190808211156112df576112df613f19565b6001600160601b038281168282160390808211156112df576112df613f19565b60208082526030908201527f6368616e6e656c206d7573742068617665207374617465204f50454e206f722060408201526f50454e44494e475f544f5f434c4f534560801b606082015260800190565b63ffffffff8181168382160190808211156112df576112df613f19565b81810381811115610dde57610dde613f19565b60008251614275818460208701613dd4565b9190910192915050565b60008261429c57634e487b7160e01b600052601260045260246000fd5b50069056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65645fa17246d3a5d68d42baa94cde33042180b783a399c02bf63ac2076e0f708738ceeab2eef998c17fe96f30f83fbf3c55fc5047f6e40c55a0cf72d236e9d2ba72a2646970667358221220657b679d135fc011c665beb7b96a5c08d8e786ec0e285b7486f2829d5e9e2ade64736f6c63430008130033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c1000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000004f7c7de3ba2b29ed8b2448df2213ca43f94e45c0

-----Decoded View---------------
Arg [0] : _token (address): 0xD4fdec44DB9D44B8f2b6d529620f9C0C7066A2c1
Arg [1] : _noticePeriodChannelClosure (uint32): 300
Arg [2] : _safeRegistry (address): 0x4F7C7dE3BA2B29ED8B2448dF2213cA43f94E45c0

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000d4fdec44db9d44b8f2b6d529620f9c0c7066a2c1
Arg [1] : 000000000000000000000000000000000000000000000000000000000000012c
Arg [2] : 0000000000000000000000004f7c7de3ba2b29ed8b2448df2213ca43f94e45c0


Deployed Bytecode Sourcemap

111817:33095:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;134639:3040;;;;;;:::i;:::-;;:::i;:::-;;137931:429;;;;;;:::i;:::-;;:::i;121076:280::-;;;;;;:::i;:::-;;:::i;130241:148::-;;;;;;:::i;:::-;;:::i;132104:178::-;;;;;;:::i;:::-;;:::i;141740:1407::-;;;;;;:::i;:::-;;:::i;:::-;;;3967:25:1;;;3955:2;3940:18;141740:1407:0;;;;;;;;16780:263;;;;;;:::i;:::-;;:::i;113305:58::-;;113361:1;113305:58;;;;;-1:-1:-1;;;;;4514:39:1;;;4496:58;;4484:2;4469:18;113305:58:0;4323:237:1;113668:104:0;;;;;130008:163;;;;;;:::i;:::-;;:::i;113184:65::-;;113240:8;113184:65;;131783:241;;;;;;:::i;:::-;;:::i;112203:92::-;;112261:34;112203:92;;113457:153;;;;;118045:43;;;;;;:::i;:::-;;;;;;;;;;;;-1:-1:-1;;;;;118045:43:0;;;-1:-1:-1;;;118045:43:0;;;;;-1:-1:-1;;;118045:43:0;;;;;-1:-1:-1;;;118045:43:0;;;;;-1:-1:-1;;;118045:43:0;;;;;;;;;;;;;;;;;:::i;128402:178::-;;;;;;:::i;:::-;;:::i;118303:53::-;;;;;;;;6504:10:1;6492:23;;;6474:42;;6462:2;6447:18;118303:53:0;6299:223:1;120330:650:0;;;:::i;143568:1341::-;;;;;;:::i;:::-;;:::i;:::-;;;7161:14:1;;7154:22;7136:41;;7124:2;7109:18;143568:1341:0;6996:187:1;10014:314:0;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;140988:169::-;141132:15;140988:169;;128081:241;;;;;;:::i;:::-;;:::i;140730:164::-;;;;;;:::i;:::-;;:::i;89889:36::-;;;;;;90613:701;;;:::i;89506:47::-;;;;;;;;;;;;;;;-1:-1:-1;;;89506:47:0;;;;;;;;;;;;:::i;113830:30::-;;;;;;118169:29;;;;;;;;-1:-1:-1;;;;;9556:32:1;;;9538:51;;9526:2;9511:18;118169:29:0;9378:217:1;138528:416:0;;;;;;:::i;:::-;;:::i;121418:256::-;;;;;;:::i;:::-;;:::i;113781:40::-;;;;;;;;;;;;;;;-1:-1:-1;;;113781:40:0;;;;;134639:3040;134968:10;-1:-1:-1;;;;;134990:5:0;134968:28;;134964:80;;135020:12;;-1:-1:-1;;;135020:12:0;;;;;;;;;;;134964:80;-1:-1:-1;;;;;135060:19:0;;135074:4;135060:19;135056:82;;135103:23;;-1:-1:-1;;;135103:23:0;;;;;;;;;;;135056:82;135150:211;;135343:7;135150:211;135434:29;135415:48;;135411:2261;;-1:-1:-1;;;;;135484:25:0;;135480:114;;;135537:41;;-1:-1:-1;;;135537:41:0;;;;;;;;;;;135480:114;135864:8;;;:24;;-1:-1:-1;;;135864:24:0;;135708:29;;135704:2;135700:38;;;135864:24;;;9538:51:1;;;135806:2:0;135785:24;;135772:38;135764:47;;;135610:11;;-1:-1:-1;;;;;135864:8:0;;;;:19;;9511:18:1;;135864:24:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;135842:46;;135995:3;-1:-1:-1;;;;;135987:11:0;:4;-1:-1:-1;;;;;135987:11:0;;135983:351;;-1:-1:-1;;;;;136079:25:0;;;136075:105;;136136:24;;-1:-1:-1;;;136136:24:0;;;;;;;;;;;136075:105;135983:351;;;136239:4;-1:-1:-1;;;;;136224:19:0;:11;-1:-1:-1;;;;;136224:19:0;;136220:99;;136275:24;;-1:-1:-1;;;136275:24:0;;;;;;;;;;;136220:99;136350:61;136371:3;136376:4;136402:6;136350:20;:61::i;:::-;135465:1023;;;135411:2261;;;136517:35;136498:54;;136494:1178;;136741:29;;136737:2;136733:38;;;;136826:4;136822:26;;136809:40;136804:3;136800:50;;;;136905:4;136901:26;;136888:40;136880:49;;;136984:4;136980:26;;136967:40;136958:50;;137043:11;;;:92;;-1:-1:-1;137068:67:0;-1:-1:-1;;;;;137103:32:0;;;;137068;;:67;:::i;:::-;137058:6;:77;;137043:92;137039:156;;;137163:16;;-1:-1:-1;;;137163:16:0;;;;;;;;;;;137039:156;-1:-1:-1;;;;;137282:27:0;;;137278:117;;137330:49;137351:8;137361;137371:7;137330:20;:49::i;:::-;-1:-1:-1;;;;;137480:27:0;;;137476:117;;137528:49;137549:8;137559;137569:7;137528:20;:49::i;:::-;136554:1050;;;;136494:1178;;;137632:28;;-1:-1:-1;;;137632:28:0;;;;;;;;;;;136494:1178;134639:3040;;;;;;;;:::o;137931:429::-;108753:11;;138034:4;;-1:-1:-1;;;108753:11:0;;;;108748:75;;108788:23;;-1:-1:-1;;;108788:23:0;;;;;;;;;;;108748:75;108839:8;;;:25;;-1:-1:-1;;;108839:25:0;;-1:-1:-1;;;;;9556:32:1;;;108839:25:0;;;9538:51:1;;;;108868:10:0;;108839:8;;;;:19;;9511:18:1;;108839:25:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;108839:39:0;;108835:103;;108902:24;;-1:-1:-1;;;108902:24:0;;;;;;;;;;;108835:103;138051:43:::1;138072:4;138078:7;138087:6;138051:20;:43::i;:::-;138163:69;::::0;-1:-1:-1;;;138163:69:0;;138182:10:::1;138163:69;::::0;::::1;11341:34:1::0;138202:4:0::1;11391:18:1::0;;;11384:43;-1:-1:-1;;;;;11463:39:1;;11443:18;;;11436:67;138163:5:0::1;-1:-1:-1::0;;;;;138163:18:0::1;::::0;::::1;::::0;11276::1;;138163:69:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:77;;138236:4;138163:77;138159:194;;138320:21;;-1:-1:-1::0;;;138320:21:0::1;;;;;;;;;;;138159:194;137931:429:::0;;;;:::o;121076:280::-;108753:11;;121279:4;;-1:-1:-1;;;108753:11:0;;;;108748:75;;108788:23;;-1:-1:-1;;;108788:23:0;;;;;;;;;;;108748:75;108839:8;;;:25;;-1:-1:-1;;;108839:25:0;;-1:-1:-1;;;;;9556:32:1;;;108839:25:0;;;9538:51:1;;;;108868:10:0;;108839:8;;;;:19;;9511:18:1;;108839:25:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;108839:39:0;;108835:103;;108902:24;;-1:-1:-1;;;108902:24:0;;;;;;;;;;;108835:103;121301:47:::1;121323:4;121329:10;121341:6;121301:21;:47::i;130241:148::-:0;109118:11;;-1:-1:-1;;;109118:11:0;;;;109113:75;;109153:23;;-1:-1:-1;;;109153:23:0;;;;;;;;;;;109113:75;109204:8;;;:31;;-1:-1:-1;;;109204:31:0;;109224:10;109204:31;;;9538:51:1;;;;109247:1:0;;-1:-1:-1;;;;;109204:8:0;;;;:19;;9511:18:1;;109204:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;109204:45:0;;109200:109;;109273:24;;-1:-1:-1;;;109273:24:0;;;;;;;;;;;109200:109;130332:49:::1;130362:10;130374:6;130332:29;:49::i;:::-;130241:148:::0;:::o;132104:178::-;109118:11;;-1:-1:-1;;;109118:11:0;;;;109113:75;;109153:23;;-1:-1:-1;;;109153:23:0;;;;;;;;;;;109113:75;109204:8;;;:31;;-1:-1:-1;;;109204:31:0;;109224:10;109204:31;;;9538:51:1;;;;109247:1:0;;-1:-1:-1;;;;;109204:8:0;;;;:19;;9511:18:1;;109204:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;109204:45:0;;109200:109;;109273:24;;-1:-1:-1;;;109273:24:0;;;;;;;;;;;109200:109;132210:64:::1;132250:10;132262:11;132210:39;:64::i;141740:1407::-:0;141823:7;141843:17;141863:53;141895:10;:20;;;141863:31;:53::i;:::-;141843:73;-1:-1:-1;142330:18:0;142675:23;;;;;;;;:::i;:::-;142652:48;;142646:2;142619:21;;;;;;;;:::i;:::-;142591:51;;:57;;142571:2;142538:27;;;;;;;;:::i;:::-;142505:62;;:68;;142484:3;142451:27;;;;;;;;:::i;:::-;142424:56;;:63;;142403:3;142375:22;;;;;;;;:::i;:::-;-1:-1:-1;;;;;142352:47:0;:54;;142351:137;:223;:298;:349;142330:370;;142813:18;142887:26;;;142959:10;:15;;:25;;;142986:10;142998:9;142942:66;;;;;;;;;13488:19:1;;;13532:2;13523:12;;13516:28;;;;13582:2;13578:15;-1:-1:-1;;;;;;13574:53:1;13569:2;13560:12;;13553:75;13653:2;13644:12;;13303:359;142942:66:0;;;;;;;-1:-1:-1;;142942:66:0;;;;;;142932:77;;142942:66;142932:77;;;;-1:-1:-1;;;;;;13857:33:1;;;;142858:166:0;;;13839:52:1;13907:18;;;13900:34;;;;142858:166:0;;;;;;;;;13812:18:1;;;142858:166:0;;142834:201;;;;;;143110:15;;-1:-1:-1;;;143065:73:0;;;14186:28:1;-1:-1:-1;;;14230:11:1;;;14223:36;14275:11;;;14268:27;14311:12;;;;14304:28;;;;143065:73:0;;;;;;;;;;14348:12:1;;;;143065:73:0;;143055:84;;;;;;141740:1407;-1:-1:-1;;;;;141740:1407:0:o;16780:263::-;16924:7;16951:35;;;;;;;;;;;-1:-1:-1;;;;;16951:44:0;;;;;;;;;;;;:84;;17030:4;16951:84;;;16566:33;16951:84;16944:91;;16780:263;;;;;:::o;130008:163::-;108753:11;;130103:4;;-1:-1:-1;;;108753:11:0;;;;108748:75;;108788:23;;-1:-1:-1;;;108788:23:0;;;;;;;;;;;108748:75;108839:8;;;:25;;-1:-1:-1;;;108839:25:0;;-1:-1:-1;;;;;9556:32:1;;;108839:25:0;;;9538:51:1;;;;108868:10:0;;108839:8;;;;:19;;9511:18:1;;108839:25:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;108839:39:0;;108835:103;;108902:24;;-1:-1:-1;;;108902:24:0;;;;;;;;;;;108835:103;130120:43:::1;130150:4;130156:6;130120:29;:43::i;:::-;130008:163:::0;;;:::o;131783:241::-;108753:11;;131936:4;;-1:-1:-1;;;108753:11:0;;;;108748:75;;108788:23;;-1:-1:-1;;;108788:23:0;;;;;;;;;;;108748:75;108839:8;;;:25;;-1:-1:-1;;;108839:25:0;;-1:-1:-1;;;;;9556:32:1;;;108839:25:0;;;9538:51:1;;;;108868:10:0;;108839:8;;;;:19;;9511:18:1;;108839:25:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;108839:39:0;;108835:103;;108902:24;;-1:-1:-1;;;108902:24:0;;;;;;;;;;;108835:103;131958:58:::1;131998:4;132004:11;131958:39;:58::i;128402:178::-:0;109118:11;;-1:-1:-1;;;109118:11:0;;;;109113:75;;109153:23;;-1:-1:-1;;;109153:23:0;;;;;;;;;;;109113:75;109204:8;;;:31;;-1:-1:-1;;;109204:31:0;;109224:10;109204:31;;;9538:51:1;;;;109247:1:0;;-1:-1:-1;;;;;109204:8:0;;;;:19;;9511:18:1;;109204:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;109204:45:0;;109200:109;;109273:24;;-1:-1:-1;;;109273:24:0;;;;;;;;;;;109200:109;128508:64:::1;128548:10;128560:11;128508:39;:64::i;120330:650::-:0;120639:21;;;;;;;;;;;-1:-1:-1;;;120639:21:0;;;;;120696:7;;;;;;;;;;-1:-1:-1;;;120696:7:0;;;;120486:298;;120515:95;120486:298;;;14630:25:1;;;;120629:32:0;14671:18:1;;;14664:34;;;;120680:25:0;14714:18:1;;;14707:34;120724:13:0;14757:18:1;;;14750:34;120764:4:0;14800:19:1;;;14793:61;-1:-1:-1;;14602:19:1;;120486:298:0;;;;;;;;;;;;120462:333;;;;;;120433:362;;120834:15;;120812:18;:37;120808:165;;120866:15;:36;;;120922:39;;120884:18;;120922:39;;;;;120370:610;120330:650::o;143568:1341::-;144073:687;;;144370:9;144073:687;;;15134:19:1;;;144330:9:0;;15169:12:1;;;15162:28;144370:9:0;;;;15206:12:1;;;15199:28;144498:20:0;;;;15243:12:1;;;15236:28;144657:20:0;;;;:22;;15280:13:1;;;;15273:29;;;;144710:23:0;;;;;15318:13:1;;;15311:29;144073:687:0;;;;;;;;;;15356:13:1;;;144073:687:0;;;144037:746;;;;;-1:-1:-1;;143983:834:0;;;;;;144877:23;;144657:20;144877:23;;;:::i;:::-;144848:53;;;;;;;;;143568:1341;-1:-1:-1;;;;143568:1341:0:o;10014:314::-;10082:22;10139:4;10127:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10117:34;;10167:9;10162:134;10182:15;;;10162:134;;;10232:52;10269:4;10276;;10281:1;10276:7;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;10232:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;10232:28:0;;-1:-1:-1;;;10232:52:0:i;:::-;10219:7;10227:1;10219:10;;;;;;;;:::i;:::-;;;;;;:65;;;;10199:3;;;;;:::i;:::-;;;;10162:134;;;;10014:314;;;;:::o;128081:241::-;108753:11;;128234:4;;-1:-1:-1;;;108753:11:0;;;;108748:75;;108788:23;;-1:-1:-1;;;108788:23:0;;;;;;;;;;;108748:75;108839:8;;;:25;;-1:-1:-1;;;108839:25:0;;-1:-1:-1;;;;;9556:32:1;;;108839:25:0;;;9538:51:1;;;;108868:10:0;;108839:8;;;;:19;;9511:18:1;;108839:25:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;108839:39:0;;108835:103;;108902:24;;-1:-1:-1;;;108902:24:0;;;;;;;;;;;108835:103;128256:58:::1;128296:4;128302:11;128256:39;:58::i;140730:164::-:0;140848:37;;-1:-1:-1;;;;;;16537:2:1;16533:15;;;16529:24;;140848:37:0;;;16517::1;16588:15;;;16584:24;16570:12;;;16563:46;140811:7:0;;16625:12:1;;140848:37:0;;;;;;;;;;;;140838:48;;;;;;140831:55;;140730:164;;;;:::o;90613:701::-;90934:19;;;;;;;;;;;-1:-1:-1;;;90934:19:0;;;;;90989:14;;;;;;;;;;-1:-1:-1;;;90989:14:0;;;;90781:303;;90810:95;90781:303;;;14630:25:1;90924:30:0;14671:18:1;;;14664:34;90973:32:0;14714:18:1;;;14707:34;91024:13:0;14757:18:1;;;14750:34;91064:4:0;14800:19:1;;;;14793:61;;;;90781:303:0;;;;;;;;;;14602:19:1;;;;90781:303:0;;;90757:338;;;;;91138:21;;91110:49;;91106:201;;91176:21;:48;;;91244:51;;91200:24;;91244:51;;;;;90659:655;90613:701::o;138528:416::-;109118:11;;-1:-1:-1;;;109118:11:0;;;;109113:75;;109153:23;;-1:-1:-1;;;109153:23:0;;;;;;;;;;;109113:75;109204:8;;;:31;;-1:-1:-1;;;109204:31:0;;109224:10;109204:31;;;9538:51:1;;;;109247:1:0;;-1:-1:-1;;;;;109204:8:0;;;;:19;;9511:18:1;;109204:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;109204:45:0;;109200:109;;109273:24;;-1:-1:-1;;;109273:24:0;;;;;;;;;;;109200:109;138627:49:::1;138648:10;138660:7;138669:6;138627:20;:49::i;:::-;138747:69;::::0;-1:-1:-1;;;138747:69:0;;138766:10:::1;138747:69;::::0;::::1;11341:34:1::0;138786:4:0::1;11391:18:1::0;;;11384:43;-1:-1:-1;;;;;11463:39:1;;11443:18;;;11436:67;138747:5:0::1;-1:-1:-1::0;;;;;138747:18:0::1;::::0;::::1;::::0;11276::1;;138747:69:0::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:77;;138820:4;138747:77;138743:194;;138904:21;;-1:-1:-1::0;;;138904:21:0::1;;;;;;;;;;;138743:194;138528:416:::0;;:::o;121418:256::-;109118:11;;-1:-1:-1;;;109118:11:0;;;;109113:75;;109153:23;;-1:-1:-1;;;109153:23:0;;;;;;;;;;;109113:75;109204:8;;;:31;;-1:-1:-1;;;109204:31:0;;109224:10;109204:31;;;9538:51:1;;;;109247:1:0;;-1:-1:-1;;;;;109204:8:0;;;;:19;;9511:18:1;;109204:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;109204:45:0;;109200:109;;109273:24;;-1:-1:-1;;;109273:24:0;;;;;;;;;;;109200:109;121613:53:::1;121635:10;121647;121659:6;121613:21;:53::i;139241:1231::-:0;139395:6;113361:1;-1:-1:-1;;;;;119835:58:0;;;119831:114;;;119917:16;;-1:-1:-1;;;119917:16:0;;;;;;;;;;;119831:114;113240:8;-1:-1:-1;;;;;119959:58:0;;;119955:139;;;120041:41;;-1:-1:-1;;;120041:41:0;;;;;;;;;;;119955:139;139435:4:::1;139441:7;119427:11;-1:-1:-1::0;;;;;119417:21:0::1;:6;-1:-1:-1::0;;;;;119417:21:0::1;::::0;119413:86:::1;;119462:25;;-1:-1:-1::0;;;119462:25:0::1;;;;;;;;;;;119413:86;-1:-1:-1::0;;;;;119513:20:0;::::1;119509:111;;119557:51;::::0;-1:-1:-1;;;119557:51:0;;16850:2:1;119557:51:0::1;::::0;::::1;16832:21:1::0;16889:2;16869:18;;;16862:30;16928:26;16908:18;;;16901:54;16972:18;;119557:51:0::1;;;;;;;;119509:111;-1:-1:-1::0;;;;;119634:25:0;::::1;119630:121;;119683:56;::::0;-1:-1:-1;;;119683:56:0;;17203:2:1;119683:56:0::1;::::0;::::1;17185:21:1::0;17242:2;17222:18;;;17215:30;17281:31;17261:18;;;17254:59;17330:18;;119683:56:0::1;17001:353:1::0;119630:121:0::1;139466:17:::2;139486:28;139500:4;139506:7;139486:13;:28::i;:::-;139525:23;139551:19:::0;;;:8:::2;:19;::::0;;;;139466:48;;-1:-1:-1;139605:30:0::2;139587:14:::0;;-1:-1:-1;;;139587:14:0;::::2;;;:48;::::0;::::2;;;;;;:::i;:::-;::::0;139583:163:::2;;139659:75;::::0;-1:-1:-1;;;139659:75:0;;17561:2:1;139659:75:0::2;::::0;::::2;17543:21:1::0;17600:2;17580:18;;;17573:30;17639:34;17619:18;;;17612:62;-1:-1:-1;;;17690:18:1;;;17683:40;17740:19;;139659:75:0::2;17359:406:1::0;139583:163:0::2;139804:15:::0;;139789:56:::2;::::0;139838:6;;-1:-1:-1;;;;;139804:15:0::2;139789:56;:::i;:::-;139758:88:::0;;-1:-1:-1;;;;;;139758:88:0::2;-1:-1:-1::0;;;;;139758:88:0;;;::::2;;::::0;;-1:-1:-1;139863:14:0;;-1:-1:-1;;;139863:14:0;::::2;;;:38;::::0;::::2;;;;;;:::i;:::-;::::0;139859:436:::2;;140024:13:::0;;140004:38:::2;::::0;-1:-1:-1;;;140024:13:0;::::2;;;140041:1;140004:38;:::i;:::-;139970:73:::0;;::::2;::::0;;;::::2;-1:-1:-1::0;;;139970:73:0::2;-1:-1:-1::0;;;;140116:35:0;-1:-1:-1;;;;140116:35:0;;;;-1:-1:-1;;;140116:35:0::2;::::0;;140179:55:::2;::::0;;140196:22:::2;140179:55;::::0;::::2;18323:19:1::0;18430:2;18426:15;;;-1:-1:-1;;;;;;18422:24:1;;;18408:12;;;18401:46;;;;18481:15;;;18477:24;;;18463:12;;;18456:46;140168:67:0::2;::::0;18518:12:1;;140179:55:0::2;;;;;;;;;;;;;140168:10;:67::i;:::-;140275:7;-1:-1:-1::0;;;;;140255:28:0::2;140269:4;-1:-1:-1::0;;;;;140255:28:0::2;;;;;;;;;;;139859:436;140380:15:::0;;140318:78:::2;::::0;140307:90:::2;::::0;140318:78:::2;::::0;-1:-1:-1;;;;;;;;;;;140335:32:0;140369:9;;-1:-1:-1;;;;;140380:15:0;;::::2;::::0;140318:78:::2;;;:::i;140307:90::-;140448:15:::0;;140413:51:::2;::::0;-1:-1:-1;;;;;140448:15:0;;::::2;4496:58:1::0;;140437:9:0;;-1:-1:-1;;;;;;;;;;;140413:51:0;4484:2:1;4469:18;140413:51:0::2;;;;;;;139455:1017;;120104:1:::1;;139241:1231:::0;;;;:::o;123915:4052::-;124117:22;;;;;;;;:::i;:::-;113361:1;-1:-1:-1;;;;;119835:58:0;;;119831:114;;;119917:16;;-1:-1:-1;;;119917:16:0;;;;;;;;;;;119831:114;113240:8;-1:-1:-1;;;;;119959:58:0;;;119955:139;;;120041:41;;-1:-1:-1;;;120041:41:0;;;;;;;;;;;119955:139;124176:10:::1;:20;;;53449:26;53472:2;53449:22;:26::i;:::-;53444:88;;53499:21;;-1:-1:-1::0;;;53499:21:0::1;;;;;;;;;;;53444:88;124257:25:::0;::::2;124214:31;124248:35:::0;;;:8:::2;:35;::::0;;;;124326:18:::2;124300:22:::0;;-1:-1:-1;;;124300:22:0;::::2;;;:44;::::0;::::2;;;;;;:::i;:::-;;;:104;;;;-1:-1:-1::0;124374:30:0::2;124348:22:::0;;-1:-1:-1;;;124348:22:0;::::2;;;:56;::::0;::::2;;;;;;:::i;:::-;;;124300:104;124296:226;;;124428:82;::::0;-1:-1:-1;;;124428:82:0;;19140:2:1;124428:82:0::2;::::0;::::2;19122:21:1::0;19179:2;19159:18;;;19152:30;19218:34;19198:18;;;19191:62;-1:-1:-1;;;19269:18:1;;;19262:47;19326:19;;124428:82:0::2;18938:413:1::0;124296:226:0::2;124604:21;::::0;;;::::2;::::0;::::2;;:::i;:::-;124558::::0;;-1:-1:-1;;;124558:21:0;::::2;124538:88;124558:21:::0;;::::2;124538:88:::0;::::2;;124534:185;;124650:57;::::0;-1:-1:-1;;;124650:57:0;;19558:2:1;124650:57:0::2;::::0;::::2;19540:21:1::0;19597:2;19577:18;;;19570:30;19636:26;19616:18;;;19609:54;19680:18;;124650:57:0::2;19356:348:1::0;124534:185:0::2;124965:16;125003:27;::::0;;;::::2;::::0;::::2;;:::i;:::-;124965:66:::0;-1:-1:-1;125042:22:0::2;125092:27;::::0;;;::::2;::::0;::::2;;:::i;:::-;125172::::0;;125042:78;;-1:-1:-1;;;;125172:27:0;::::2;;;125233:1;125215:19;::::0;::::2;;::::0;:47:::2;;;125250:12;125238:24;;:9;:24;;;125215:47;125211:120;;;125286:33;;-1:-1:-1::0;;;125286:33:0::2;;;;;;;;;;;125211:120;125404:22;::::0;;;::::2;::::0;::::2;;:::i;:::-;125362:23:::0;;-1:-1:-1;;;;;125347:80:0;;::::2;125362:23:::0;::::2;125347:80;125343:148;;;125451:28;;-1:-1:-1::0;;;125451:28:0::2;;;;;;;;;;;125343:148;125603:18;125624:26;125639:10;125624:14;:26::i;:::-;125603:47;;125668:48;125685:10;125697;125709:6;125668:16;:48::i;:::-;125663:106;;125740:17;;-1:-1:-1::0;;;125740:17:0::2;;;;;;;;;;;125663:106;125781:36;125833:74;;;;;;;;125855:10;125833:74;;;;125867:4;-1:-1:-1::0;;;;;125833:74:0::2;;;;;125890:15;;125873:33;;;;;;19838:19:1::0;;19882:2;19873:12;;19709:182;125873:33:0::2;;::::0;;-1:-1:-1;;125873:33:0;;::::2;::::0;;;;;;125833:74;;125781:126;-1:-1:-1;125925:26:0::2;;;::::0;;::::2;::::0;::::2;125935:6:::0;125925:26:::2;:::i;:::-;125943:7;125925:9;:26::i;:::-;125920:84;;125975:17;;-1:-1:-1::0;;;125975:17:0::2;;;;;;;;;;;125920:84;126016:14;126033:74;126047:10:::0;126059:20:::2;::::0;::::2;:22;126083:23:::0;;;::::2;126033:13;:74::i;:::-;126016:91:::0;-1:-1:-1;126153:25:0;::::2;126122:27;126016:91:::0;126144:4;126122:13:::2;:27::i;:::-;:56;126118:120;;126202:24;;-1:-1:-1::0;;;126202:24:0::2;;;;;;;;;;;126118:120;126297:27;;::::0;::::2;:9:::0;:27:::2;:::i;:::-;126250:75:::0;;::::2;::::0;;;::::2;-1:-1:-1::0;;;126250:75:0::2;-1:-1:-1::0;;;;126250:75:0;;::::2;;::::0;;126445:22:::2;::::0;;;::::2;::::0;::::2;;:::i;:::-;126403:23:::0;;126388:80:::2;::::0;;-1:-1:-1;;;;;126403:23:0::2;126388:80;:::i;:::-;126336:133:::0;;-1:-1:-1;;;;;;126336:133:0::2;-1:-1:-1::0;;;;;126336:133:0;;;::::2;::::0;;::::2;::::0;;126505:102:::2;::::0;126480:138:::2;::::0;126505:102:::2;::::0;126522:32:::2;::::0;126556:25;::::2;::::0;126505:102:::2;;;:::i;126480:138::-;126685:23:::0;;126634:75:::2;::::0;-1:-1:-1;;;;;126685:23:0;;::::2;4496:58:1::0;;126658:25:0;::::2;::::0;126634:75:::2;::::0;4484:2:1;4469:18;126634:75:0::2;;;;;;;126722:25;126750:27;126764:4;126770:6;126750:13;:27::i;:::-;126722:55;;126788:30;126821:8;:27;126830:17;126821:27;;;;;;;;;;;126788:60;;126903:109;126931:23;126956:10;:15;;:25;;;126983:15;:27;;;;;;;;;;;;126914:97;;;;;;;;;21527:19:1::0;;;21571:2;21562:12;;21555:28;;;;21639:3;21617:16;-1:-1:-1;;;;;;21613:47:1;21608:2;21599:12;;21592:69;21686:2;21677:12;;21311:384;126903:109:0::2;127070:27:::0;;127028:70:::2;::::0;-1:-1:-1;;;127070:27:0;;::::2;;;21877:46:1::0;;127043:25:0;::::2;::::0;127028:70:::2;::::0;21865:2:1;21850:18;127028:70:0::2;;;;;;;127140:20;127115:21:::0;;-1:-1:-1;;;127115:21:0;::::2;;;:45;::::0;::::2;;;;;;:::i;:::-;::::0;127111:849:::2;;127269:5;-1:-1:-1::0;;;;;127269:14:0::2;;127284:10;127311;:15;;:22;;;;;;;;;;:::i;:::-;127269:66;::::0;-1:-1:-1;;;;;;127269:66:0::2;::::0;;;;;;-1:-1:-1;;;;;22125:32:1;;;127269:66:0::2;::::0;::::2;22107:51:1::0;-1:-1:-1;;;;;22194:39:1;22174:18;;;22167:67;22080:18;;127269:66:0::2;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:74;;127339:4;127269:74;127265:143;;127371:21;;-1:-1:-1::0;;;127371:21:0::2;;;;;;;;;;;127265:143;127111:849;;;127718:22;::::0;;;::::2;::::0;::::2;;:::i;:::-;127677::::0;;127662:79:::2;::::0;;-1:-1:-1;;;;;127677:22:0::2;127662:79;:::i;:::-;127607:135:::0;;-1:-1:-1;;;;;;127607:135:0::2;-1:-1:-1::0;;;;;127607:135:0;;;::::2;::::0;;::::2;::::0;;127768:93:::2;::::0;127757:105:::2;::::0;127768:93:::2;::::0;-1:-1:-1;;;;;;;;;;;127785:32:0;127819:17;;127768:93:::2;;;:::i;127757:105::-;127925:22:::0;;127882:66:::2;::::0;-1:-1:-1;;;;;127925:22:0;;::::2;4496:58:1::0;;127906:17:0;;-1:-1:-1;;;;;;;;;;;127882:66:0;4484:2:1;4469:18;127882:66:0::2;;;;;;;127111:849;124203:3764;;;;;;;;;120104:1:::1;123915:4052:::0;;;;:::o;130632:1037::-;130778:17;130798:27;130812:6;130820:4;130798:13;:27::i;:::-;130838:23;130864:19;;;:8;:19;;;;;130778:47;;-1:-1:-1;130900:14:0;;-1:-1:-1;;;130900:14:0;;;;:38;;;;;;;;:::i;:::-;;130896:159;;130962:81;;-1:-1:-1;;;130962:81:0;;;;;;;:::i;130896:159::-;131100:15;;-1:-1:-1;;;;;;131308:33:0;;;;131406:51;;;-1:-1:-1;;;;;;;;;;;131406:51:0;;;22819:19:1;22854:12;;;22847:28;;;-1:-1:-1;;;;;131100:15:0;;;;131395:63;;22891:12:1;;131406:51:0;22662:247:1;131395:63:0;131474:24;;131488:9;;-1:-1:-1;;;;;;;;;;;131474:24:0;;;;131515:11;;131511:151;;131547:31;;-1:-1:-1;;;131547:31:0;;-1:-1:-1;;;;;23106:32:1;;;131547:31:0;;;23088:51:1;23155:18;;;23148:34;;;131547:5:0;:14;;;;23061:18:1;;131547:31:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:39;;131582:4;131547:39;131543:108;;131614:21;;-1:-1:-1;;;131614:21:0;;;;;;;;;;;131543:108;130710:959;;;130632:1037;;:::o;132474:1219::-;132640:17;132660:32;132674:4;132680:11;132660:13;:32::i;:::-;132703:23;132729:19;;;:8;:19;;;;;132640:52;;-1:-1:-1;132783:30:0;132765:14;;-1:-1:-1;;;132765:14:0;;;;:48;;;;;;;;:::i;:::-;;132761:159;;132837:71;;-1:-1:-1;;;132837:71:0;;23395:2:1;132837:71:0;;;23377:21:1;23434:2;23414:18;;;23407:30;23473:34;23453:18;;;23446:62;-1:-1:-1;;;23524:18:1;;;23517:36;23570:19;;132837:71:0;23193:402:1;132761:159:0;132953:19;;132936:83;141132:15;132936:83;;-1:-1:-1;;;132953:19:0;;;;132936:83;132932:143;;133043:20;;-1:-1:-1;;;133043:20:0;;;;;;;;;;;132932:143;133120:15;;-1:-1:-1;;;;;;133328:33:0;;;;133426:51;;;-1:-1:-1;;;;;;;;;;;133426:51:0;;;22819:19:1;22854:12;;;22847:28;;;-1:-1:-1;;;;;133120:15:0;;;;133415:63;;22891:12:1;;133426:51:0;22662:247:1;133415:63:0;133494:24;;133508:9;;-1:-1:-1;;;;;;;;;;;133494:24:0;;;;133535:11;;133531:155;;133567:35;;-1:-1:-1;;;133567:35:0;;133582:10;133567:35;;;23088:51:1;23155:18;;;23148:34;;;133567:5:0;-1:-1:-1;;;;;133567:14:0;;;;23061:18:1;;133567:35:0;22914:274:1;54123:343:0;54192:7;54219:239;54192:7;49598:2;49390:66;-1:-1:-1;;49390:66:0;54382:6;54375:71;54219:239;;;54367:80;54219:239;;;;;;;;23967:25:1;;;;24040:4;24028:17;;;24008:18;;;24001:45;;;;24062:18;;;24055:34;24105:18;;;24098:34;23939:19;;54219:239:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;54219:239:0;;-1:-1:-1;;54219:239:0;;;54123:343;-1:-1:-1;;;54123:343:0:o;128890:1014::-;129056:17;129076:32;129090:4;129096:11;129076:13;:32::i;:::-;129119:23;129145:19;;;:8;:19;;;;;129056:52;;-1:-1:-1;129272:14:0;;-1:-1:-1;;;129272:14:0;;;;:38;;;;;;;;:::i;:::-;;129268:159;;129334:81;;-1:-1:-1;;;129334:81:0;;;;;;;:::i;129268:159::-;129489:89;129551:26;141132:15;129489:89;:::i;:::-;129439:140;;-1:-1:-1;;;;;;;129590:47:0;;;-1:-1:-1;;;;;;;129439:140:0;;;;;;;129590:47;;;;;;129726:90;;;129743:40;129726:90;;;24534:19:1;24569:12;;;24562:28;;;129796:19:0;;;24646:3:1;24624:16;-1:-1:-1;;;;;;24624:16:1;24606:12;;;24599:65;129715:102:0;;24680:12:1;;129726:90:0;24320:378:1;129715:102:0;129876:19;;129833:63;;-1:-1:-1;;;129876:19:0;;;;;6474:42:1;;129865:9:0;;129833:63;;6462:2:1;6447:18;129833:63:0;;;;;;;128983:921;;128890:1014;;:::o;6920:200::-;7003:12;7035:77;7056:6;7064:4;7035:77;;;;;;;;;;;;;;;;;:20;:77::i;91322:986::-;91444:10;:20;91384:19;;91444:39;;91467:16;;-1:-1:-1;;;91444:20:0;;;;:39;:::i;:::-;91426:15;:57;91422:111;;;-1:-1:-1;91517:4:0;91422:111;91733:21;;91976:10;:19;92086:18;;91976:19;92086:18;;;;;;;91649:474;;;;;;24934:25:1;;;;91888:12:0;24975:18:1;;;24968:34;91976:19:0;;;-1:-1:-1;;25038:28:1;25018:18;;;25011:56;25083:18;;;25076:34;24906:19;;91649:474:0;;;;;;-1:-1:-1;;91649:474:0;;;;;;91621:517;;91649:474;91621:517;;;;91577:10;:572;;-1:-1:-1;;;;;;91577:572:0;;;;;;;;92162:139;;;;92218:10;92197:31;;;-1:-1:-1;;;92197:31:0;;;;;;;-1:-1:-1;;;;;92197:31:0;;;;;;:18;:31;92273:15;92243:46;;;;;;;91373:935;91322:986;:::o;52330:136::-;52397:4;52421:7;;;:37;;-1:-1:-1;;;;;52432:26:0;52330:136::o;86837:1602::-;86935:4;-1:-1:-1;;86956:6:0;:8;;;:38;;:80;;;;-1:-1:-1;;86998:6:0;:8;;;:38;;86956:80;86952:141;;;87060:21;;-1:-1:-1;;;87060:21:0;;;;;;;;;;;86952:141;87110:42;87131:6;:9;;;87142:6;:9;;;87110:20;:42::i;:::-;87105:102;;87176:19;;-1:-1:-1;;;87176:19:0;;;;;;;;;;;87105:102;87267:10;87279;87293:75;87322:7;:14;;;87338:7;:15;;;87305:49;;;;;;;;25298:2:1;25294:15;;;;-1:-1:-1;;;;;;25290:53:1;25278:66;;25369:2;25360:12;;25353:28;25406:2;25397:12;;25121:294;87305:49:0;;;;;;;;;;;;;87356:7;:11;;;87293;:75::i;:::-;87266:102;;;;87512:16;87531:43;87557:6;:8;;;87567:2;87571;87531:25;:43::i;:::-;87512:62;;87603:38;87618:6;:10;;;87630:6;:10;;;55864:24;;;;;;;22819:19:1;;;;22854:12;;;22847:28;;;;55864:24:0;;;;;;;;;22891:12:1;;;;55864:24:0;;55854:35;;;;;;55732:168;87603:38;-1:-1:-1;;;;;87591:50:0;:8;-1:-1:-1;;;;;87591:50:0;;87587:111;;87665:21;;-1:-1:-1;;;87665:21:0;;;;;;;;;;;87587:111;87710:15;87728:57;87754:6;:8;;;87764:6;:9;;;87775:6;:9;;;87728:25;:57::i;:::-;87710:75;;87813:38;87828:6;:10;;;87840:6;:10;;;55864:24;;;;;;;22819:19:1;;;;22854:12;;;22847:28;;;;55864:24:0;;;;;;;;;22891:12:1;;;;55864:24:0;;55854:35;;;;;;55732:168;87813:38;-1:-1:-1;;;;;87802:49:0;:7;-1:-1:-1;;;;;87802:49:0;;87798:110;;87875:21;;-1:-1:-1;;;87875:21:0;;;;;;;;;;;87798:110;88133:10;88145;88159:85;88165:6;:10;;;88177:6;:10;;;88189:6;:10;;;88230:6;:10;;;-1:-1:-1;;88201:39:0;;;;:::i;:::-;88242:1;88159:5;:85::i;:::-;88317:14;;;;;88333:9;;88344;;;;88363:15;;88300:79;;88132:112;;-1:-1:-1;88132:112:0;;-1:-1:-1;88257:14:0;;88287:106;;88300:79;;88132:112;;;;88363:15;88300:79;25842:2:1;25838:15;;;;-1:-1:-1;;;;;;25834:53:1;25822:66;;25913:2;25904:12;;25897:28;;;;25950:2;25941:12;;25934:28;;;;25987:2;25978:12;;25971:28;26024:3;26015:13;;26008:29;26062:3;26053:13;;26046:29;26100:3;26091:13;;25553:557;88300:79:0;;;;;;;;;;;;;88381:7;:11;;;88287:12;:106::i;:::-;88423:8;;;;88413:18;;-1:-1:-1;;;;;;;;86837:1602:0;;;;:::o;43138:226::-;43215:7;43236:17;43255:18;43277:23;43288:4;43294:1;43297:2;43277:10;:23::i;:::-;43235:65;;;;43311:18;43323:5;43311:11;:18::i;:::-;-1:-1:-1;43347:9:0;43138:226;-1:-1:-1;;;;43138:226:0:o;7314:332::-;7459:12;7485;7499:23;7526:6;-1:-1:-1;;;;;7526:19:0;7546:4;7526:25;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7484:67;;;;7569:69;7596:6;7604:7;7613:10;7625:12;7569:26;:69::i;:::-;7562:76;7314:332;-1:-1:-1;;;;;;7314:332:0:o;52662:555::-;52739:6;-1:-1:-1;;53077:26:0;53073:2;-1:-1:-1;;53040:2:0;53036;53029:42;53022:82;52984:11;52951:229;-1:-1:-1;;52897:2:0;52893;52886:42;52861:338;;52662:555;-1:-1:-1;;;52662:555:0:o;62034:518::-;62118:10;62130;62154;62166;62180:27;62194:7;62203:3;62180:13;:27::i;:::-;62153:54;;;;62221:11;62234;62249:32;62277:2;62249:19;:32::i;:::-;62220:61;;;;62315:11;62328;62343:32;62371:2;62343:19;:32::i;:::-;62314:61;;;;62448:10;62460;62474:34;62480:3;62485;62490;62495;49737:66;62474:5;:34::i;:::-;62447:61;;;;62528:16;62537:2;62541;62528:8;:16::i;:::-;62521:23;;;;;;;;;;;;62034:518;;;;;;:::o;55118:344::-;55216:7;;55261:6;55266:1;55261:2;:6;:::i;:::-;55271:1;55261:11;55257:95;;-1:-1:-1;55296:2:0;55257:95;;;-1:-1:-1;55338:2:0;55257:95;55371:83;55381:1;55384:4;55398:2;-1:-1:-1;;55426:2:0;55418:6;55411:41;55371:83;;;55403:50;55371:83;;;;;;;;23967:25:1;;;;24040:4;24028:17;;;24008:18;;;24001:45;;;;24062:18;;;24055:34;24105:18;;;24098:34;23939:19;;55371:83:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;55371:83:0;;-1:-1:-1;;55371:83:0;;;55118:344;-1:-1:-1;;;;;;55118:344:0:o;56867:4599::-;57043:10;57055;57194:2;57190;57187:10;57183:15;57178:2;57174;57171:10;57167:32;57164:182;;;57329:1;57326;57319:12;57164:182;57360:10;57384:12;57440:2;57436;57433:10;57428:2;57424;57421:10;57417:27;57488:4;57483:1302;;;;58799:1557;;;;57410:2946;;57483:1302;-1:-1:-1;;57607:1:0;-1:-1:-1;;57574:2:0;57571:1;57564:41;57557:80;57545:92;;57756:4;57750:11;57795:4;57786:7;57779:21;57863:4;57856;57847:7;57843:18;57836:32;57935:4;57928;57919:7;57915:18;57908:32;58006:8;57999:4;57990:7;57986:18;57979:36;-1:-1:-1;;58061:4:0;58052:7;58048:18;58041:94;-1:-1:-1;;58182:4:0;58173:7;58169:18;58162:54;58304:4;58295:7;58289:4;58280:7;58274:4;58270:1;58266:6;58255:54;58245:170;;58394:1;58391;58384:12;58245:170;-1:-1:-1;;58686:7:0;58680:14;-1:-1:-1;;58597:26:0;58593:2;58589;58582:42;58579:1;58531:122;58464:306;58433:337;;;57483:1302;;58799:1557;-1:-1:-1;;59011:2:0;-1:-1:-1;;58979:35:0;58943:2;58897:202;58864:235;;59209:4;59203:11;59248:4;59239:7;59232:21;59316:4;59309;59300:7;59296:18;59289:32;59388:4;59381;59372:7;59368:18;59361:32;59459:8;59452:4;59443:7;59439:18;59432:36;-1:-1:-1;;59514:4:0;59505:7;59501:18;59494:94;-1:-1:-1;;59635:4:0;59626:7;59622:18;59615:54;59757:4;59748:7;59742:4;59733:7;59727:4;59723:1;59719:6;59708:54;59698:170;;59847:1;59844;59837:12;59698:170;-1:-1:-1;;60237:7:0;60231:14;-1:-1:-1;;60108:2:0;-1:-1:-1;;60076:35:0;60036:2;59986:218;59919:422;59888:453;;;57410:2946;;;-1:-1:-1;;60723:26:0;60684:2;-1:-1:-1;;60652:35:0;60613:2;-1:-1:-1;;60581:35:0;60533:239;-1:-1:-1;;60463:6:0;60455;60448:50;60395:445;60372:468;;-1:-1:-1;;61377:2:0;-1:-1:-1;;61345:35:0;-1:-1:-1;;61193:26:0;61150:2;-1:-1:-1;;61118:35:0;61078:2;61028:218;60995:6;60938:384;60879:569;60856:592;;;56867:4599;;;;;;;;:::o;75953:884::-;76038:9;76061:10;76073;76087:49;76123:7;76132:3;76087:35;:49::i;:::-;76060:76;;;;76305:4;76299:11;76359:4;76356:1;76349:15;76417:4;76410;76407:1;76403:12;76396:26;76479:4;76472;76469:1;76465:12;76458:26;76540:2;76533:4;76530:1;76526:12;76519:24;76586:2;76579:4;76576:1;76572:12;76565:24;76624:1;76617:4;76614:1;76610:12;76603:23;-1:-1:-1;;76666:4:0;76663:1;76659:12;76652:43;76767:4;76764:1;76758:4;76755:1;76749:4;76745:1;76741:6;76730:42;76720:70;;76786:1;76783;76776:12;76720:70;76811:8;;75953:884;-1:-1:-1;;;;;75953:884:0:o;42656:310::-;42736:7;;-1:-1:-1;;;;;42782:80:0;;42736:7;42889:25;42905:3;42890:18;;;42912:2;42889:25;:::i;:::-;42873:42;;42933:25;42944:4;42950:1;42953;42956;42933:10;:25::i;:::-;42926:32;;;;;;42656:310;;;;;;:::o;39006:521::-;39084:20;39075:5;:29;;;;;;;;:::i;:::-;;39071:449;;39006:521;:::o;39071:449::-;39182:29;39173:5;:38;;;;;;;;:::i;:::-;;39169:351;;39228:34;;-1:-1:-1;;;39228:34:0;;27075:2:1;39228:34:0;;;27057:21:1;27114:2;27094:18;;;27087:30;27153:26;27133:18;;;27126:54;27197:18;;39228:34:0;26873:348:1;39169:351:0;39293:35;39284:5;:44;;;;;;;;:::i;:::-;;39280:240;;39345:41;;-1:-1:-1;;;39345:41:0;;27428:2:1;39345:41:0;;;27410:21:1;27467:2;27447:18;;;27440:30;27506:33;27486:18;;;27479:61;27557:18;;39345:41:0;27226:355:1;39280:240:0;39417:30;39408:5;:39;;;;;;;;:::i;:::-;;39404:116;;39464:44;;-1:-1:-1;;;39464:44:0;;27788:2:1;39464:44:0;;;27770:21:1;27827:2;27807:18;;;27800:30;27866:34;27846:18;;;27839:62;-1:-1:-1;;;27917:18:1;;;27910:32;27959:19;;39464:44:0;27586:398:1;7942:644:0;8127:12;8156:7;8152:427;;;8184:10;:17;8205:1;8184:22;8180:290;;-1:-1:-1;;;;;1823:19:0;;;8394:60;;;;-1:-1:-1;;;8394:60:0;;28191:2:1;8394:60:0;;;28173:21:1;28230:2;28210:18;;;28203:30;28269:31;28249:18;;;28242:59;28318:18;;8394:60:0;27989:353:1;8394:60:0;-1:-1:-1;8491:10:0;8484:17;;8152:427;8534:33;8542:10;8554:12;8534:7;:33::i;:::-;7942:644;;;;;;:::o;73891:1578::-;73977:10;73989;74013;74025;74037;74051:42;74080:7;74089:3;74051:28;:42::i;:::-;74012:81;;;;;;74259:4;74253:11;74313:4;74310:1;74303:15;74371:4;74364;74361:1;74357:12;74350:26;74433:4;74426;74423:1;74419:12;74412:26;74494:2;74487:4;74484:1;74480:12;74473:24;74540:2;74533:4;74530:1;74526:12;74519:24;74578:1;74571:4;74568:1;74564:12;74557:23;-1:-1:-1;;74620:4:0;74617:1;74613:12;74606:48;74726:4;74723:1;74717:4;74714:1;74708:4;74704:1;74700:6;74689:42;74679:70;;74745:1;74742;74735:12;74679:70;74777:1;74771:8;74765:14;;;74956:4;74950:11;74985:4;74982:1;74975:15;75043:4;75036;75033:1;75029:12;75022:26;75105:2;75098:4;75095:1;75091:12;75084:24;75143:4;75136;75133:1;75129:12;75122:26;75204:2;75197:4;75194:1;75190:12;75183:24;75250:1;75243:4;75240:1;75236:12;75229:23;-1:-1:-1;;75292:4:0;75289:1;75285:12;75278:48;75398:4;75395:1;75389:4;75386:1;75380:4;75376:1;75372:6;75361:42;75351:70;;75417:1;75414;75407:12;75351:70;75449:1;75443:8;75437:14;;;74926:536;;;73891:1578;;;;;:::o;67980:5411::-;68043:10;68055;-1:-1:-1;;68180:1:0;68177;68170:40;-1:-1:-1;;68258:3:0;-1:-1:-1;;68248:42:0;68241:49;;-1:-1:-1;;68348:3:0;68343;68336:44;-1:-1:-1;;68432:3:0;68427;68420:44;68413:51;;-1:-1:-1;;68524:1:0;68519:3;68512:42;-1:-1:-1;;68608:7:0;68603:3;68596:48;68589:55;-1:-1:-1;68681:7:0;68709:10;;68788:4;68783:22;;;;68819:58;;;;68702:175;;68783:22;-1:-1:-1;;68795:8:0;;68783:22;;68819:58;68871:3;-1:-1:-1;;68839:36:0;68832:43;;68702:175;;-1:-1:-1;;68916:3:0;68907:7;68900:48;68893:55;;-1:-1:-1;;69002:3:0;68997;68990:44;68983:51;;-1:-1:-1;;69090:3:0;69085;69078:44;-1:-1:-1;;69182:3:0;69173:7;69166:48;-1:-1:-1;;69268:3:0;69263;69256:44;69249:51;;-1:-1:-1;;69356:3:0;69351;69344:44;69337:51;;-1:-1:-1;;69444:3:0;69439;69432:44;69425:51;;-1:-1:-1;;69536:3:0;69527:7;69520:48;69513:55;;-1:-1:-1;;69622:3:0;69617;69610:44;69603:51;;;-1:-1:-1;;69709:3:0;69704;69697:44;69691:50;;69843:6;69863:12;-1:-1:-1;;70307:3:0;70302;70295:44;-1:-1:-1;;70396:3:0;70391;70384:44;-1:-1:-1;;70485:3:0;70480;70473:44;70466:51;;70635:4;70629:11;70668:4;70665:1;70658:15;70730:4;70723;70720:1;70716:12;70709:26;70796:4;70789;70786:1;70782:12;70775:26;70861:3;70854:4;70851:1;70847:12;70840:25;-1:-1:-1;;;;;70905:4:0;70902:1;70898:12;70891:25;-1:-1:-1;;70960:4:0;70957:1;70953:12;70946:48;71070:4;71067:1;71061:4;71058:1;71052:4;71048:1;71044:6;71033:42;71023:158;;71160:1;71157;71150:12;71023:158;-1:-1:-1;;71233:3:0;71229:1;71223:8;71216:49;71201:64;;;;-1:-1:-1;;71334:3:0;71325:7;71318:48;-1:-1:-1;;71430:7:0;71421;71414:52;-1:-1:-1;;71520:3:0;71515;71508:44;71501:51;;71607:3;71602;71599:12;71673:4;71668:154;;;;71840:105;;;;71592:353;;71668:154;71764:4;71752:16;;71796:7;71790:13;;71668:154;;71840:105;71886:5;71874:17;;71919:7;71913:13;;71592:353;;;;;-1:-1:-1;;72050:1:0;72045:3;72038:42;72032:48;;-1:-1:-1;;72132:2:0;72128;72121:42;72115:48;;72202:8;72199:162;;;72236:3;72230:9;;72304:2;72298:8;;72199:162;;;;72454:1;72450:2;72446:10;72442:1;72439;72435:9;72432:25;72422:178;;72553:2;-1:-1:-1;;72521:35:0;72515:41;;72422:178;72670:4;72664:11;72649:26;;72705:4;72696:7;72689:21;72769:4;72762;72753:7;72749:18;72742:32;72837:4;72830;72821:7;72817:18;72810:32;72904:3;72897:4;72888:7;72884:18;72877:31;;;;-1:-1:-1;;72950:4:0;72941:7;72937:18;72930:94;-1:-1:-1;;73065:4:0;73056:7;73052:18;73045:54;73177:4;73168:7;73162:4;73153:7;73147:4;73143:1;73139:6;73128:54;73118:158;;73259:1;73256;73249:12;73118:158;-1:-1:-1;;73315:7:0;73309:14;73305:2;73298:54;73292:60;;;67980:5411;;;:::o;63384:3971::-;63449:10;63461;-1:-1:-1;;63592:2:0;63588;63581:42;-1:-1:-1;;63676:8:0;63672:2;63665:48;-1:-1:-1;;64202:26:0;64196:4;-1:-1:-1;;64163:2:0;64157:4;64150:44;64143:86;-1:-1:-1;;64017:26:0;64007:8;64001:4;63994:50;-1:-1:-1;;63931:7:0;63925:4;63918:49;63885:235;63856:441;-1:-1:-1;;64537:4:0;-1:-1:-1;;64458:26:0;64454:2;64448:4;64441:44;64431:8;64424:90;64395:214;64705:4;64699:11;64740:4;64731:7;64724:21;64804:4;64797;64788:7;64784:18;64777:32;64872:4;64865;64856:7;64852:18;64845:32;64939:4;64932;64923:7;64919:18;64912:32;-1:-1:-1;;64986:4:0;64977:7;64973:18;64966:94;-1:-1:-1;;65103:4:0;65094:7;65090:18;65083:54;65221:4;65212:7;65206:4;65197:7;65191:4;65187:1;65183:6;65172:54;65162:158;;65303:1;65300;65293:12;65162:158;65350:7;65344:14;65336:22;;-1:-1:-1;;65425:4:0;65419;65412:46;65406:52;;-1:-1:-1;;65931:26:0;65925:4;-1:-1:-1;;65892:2:0;65886:4;65879:44;65872:86;-1:-1:-1;;65746:26:0;65736:8;65730:4;65723:50;-1:-1:-1;;65660:7:0;65654:4;65647:49;65614:235;65585:441;65555:471;;-1:-1:-1;;66349:26:0;-1:-1:-1;;;;66310:2:0;66304:4;66297:44;66290:86;-1:-1:-1;;66211:26:0;66201:8;66195:4;66188:50;66179:7;66172:95;66143:301;66113:331;;66539:4;66533:11;66522:22;;66574:4;66565:7;66558:21;66638:4;66631;66622:7;66618:18;66611:32;66706:4;66699;66690:7;66686:18;66679:32;66773:5;66766:4;66757:7;66753:18;66746:33;-1:-1:-1;;66821:4:0;66812:7;66808:18;66801:94;-1:-1:-1;;66938:4:0;66929:7;66925:18;66918:54;67050:4;67041:7;67035:4;67026:7;67020:4;67016:1;67012:6;67001:54;66991:158;;67132:1;67129;67122:12;66991:158;67174:14;;-1:-1:-1;;;67310:26:0;-1:-1:-1;67174:14:0;67310:26;67267:5;67263:2;67256:45;67249:88;67243:94;;;;;63384:3971;;;;;:::o;81882:3280::-;82042:10;82054;82181:3;82175;82169:10;82166:19;82163:39;;;82198:1;82195;82188:12;82163:39;82218:6;82327:4;82321:11;82428:19;82505:4;82678:1;82663:271;82694:7;82688:14;82685:1;82682:21;82663:271;;;82790:19;;;82784:26;82756;;;82749:62;82863:4;82847:21;;;;82899:16;;;;82711:12;82663:271;;;82667:14;;83095:3;83085:7;83079:14;83075:24;83061:38;;83153:4;83140:10;83129:9;83125:26;83117:41;83285:1;83269:18;83233:4;83472:1;83457:260;83488:3;83482:10;83479:1;83476:17;83457:260;;;83580:14;;;83574:21;83546:26;;;83539:57;83648:4;83632:21;;;;83683:15;;;;83501:12;83457:260;;;83461:14;;83940:3;83933;83927:10;83917:7;83911:14;83907:31;83903:41;83889:55;;84004:3;83998:10;83985;83974:9;83970:26;83962:47;;84087:7;84081:14;84075:3;84069:10;84065:31;84060:3;84056:41;84045:9;84035:63;84029:69;;;84205:4;84199:11;84242:2;84231:9;84224:21;84325:1;84318:4;84307:9;84303:20;84295:32;84359:4;84389;84486:1;84471:238;84502:3;84496:10;84493:1;84490:17;84471:238;;;84588:14;;;84582:21;84556:24;;;84549:55;84648:4;84634:19;;;;84679:15;;;;84515:12;84471:238;;;84475:14;;;84842:3;84836:10;84828:3;84822:10;84816:4;84812:21;84801:9;84797:37;84789:58;84904:3;84898:10;84894:2;84890:19;84879:9;84869:41;84863:47;;84995:2;84991;84987:11;84976:9;84969:30;85079:1;85072:4;85061:9;85057:20;85049:32;85138:3;85132:10;85128:2;85124:19;85113:9;85103:41;85097:47;;;;81882:3280;;;;;:::o;43546:1477::-;43634:7;;44568:66;44555:79;;44551:163;;;-1:-1:-1;44667:1:0;;-1:-1:-1;44671:30:0;44651:51;;44551:163;44828:24;;;44811:14;44828:24;;;;;;;;;23967:25:1;;;24040:4;24028:17;;24008:18;;;24001:45;;;;24062:18;;;24055:34;;;24105:18;;;24098:34;;;44828:24:0;;23939:19:1;;44828:24:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;44828:24:0;;-1:-1:-1;;44828:24:0;;;-1:-1:-1;;;;;;;44867:20:0;;44863:103;;44920:1;44924:29;44904:50;;;;;;;44863:103;44986:6;-1:-1:-1;44994:20:0;;-1:-1:-1;43546:1477:0;;;;;;;;:::o;9128:552::-;9289:17;;:21;9285:388;;9521:10;9515:17;9578:15;9565:10;9561:2;9557:19;9550:44;9285:388;9648:12;9641:20;;-1:-1:-1;;;9641:20:0;;;;;;;;:::i;77603:3519::-;77756:10;77768;77780;77907:3;77901;77895:10;77892:19;77889:39;;;77924:1;77921;77914:12;77889:39;77944:6;78053:4;78047:11;78154:19;78231:4;78404:1;78389:271;78420:7;78414:14;78411:1;78408:21;78389:271;;;78516:19;;;78510:26;78482;;;78475:62;78589:4;78573:21;;;;78625:16;;;;78437:12;78389:271;;;78393:14;;78821:3;78811:7;78805:14;78801:24;78787:38;;78879:4;78866:10;78855:9;78851:26;78843:41;79011:1;78995:18;78959:4;79198:1;79183:260;79214:3;79208:10;79205:1;79202:17;79183:260;;;79306:14;;;79300:21;79272:26;;;79265:57;79374:4;79358:21;;;;79409:15;;;;79227:12;79183:260;;;79187:14;;79666:3;79659;79653:10;79643:7;79637:14;79633:31;79629:41;79615:55;;79730:3;79724:10;79711;79700:9;79696:26;79688:47;;79813:7;79807:14;79801:3;79795:10;79791:31;79786:3;79782:41;79771:9;79761:63;79755:69;;;79931:4;79925:11;79968:2;79957:9;79950:21;80051:1;80044:4;80033:9;80029:20;80021:32;80085:4;80115;80212:1;80197:238;80228:3;80222:10;80219:1;80216:17;80197:238;;;80314:14;;;80308:21;80282:24;;;80275:55;80374:4;80360:19;;;;80405:15;;;;80241:12;80197:238;;;80201:14;;;80568:3;80562:10;80554:3;80548:10;80542:4;80538:21;80527:9;80523:37;80515:58;80630:3;80624:10;80620:2;80616:19;80605:9;80595:41;80589:47;;80721:2;80717;80713:11;80702:9;80695:30;80805:1;80798:4;80787:9;80783:20;80775:32;80864:3;80858:10;80854:2;80850:19;80839:9;80829:41;80823:47;;80955:2;80951;80947:11;80936:9;80929:30;81039:1;81032:4;81021:9;81017:20;81009:32;81098:3;81092:10;81088:2;81084:19;81073:9;81063:41;81057:47;;;;77603:3519;;;;;:::o;14:131:1:-;-1:-1:-1;;;;;89:31:1;;79:42;;69:70;;135:1;132;125:12;150:347;201:8;211:6;265:3;258:4;250:6;246:17;242:27;232:55;;283:1;280;273:12;232:55;-1:-1:-1;306:20:1;;349:18;338:30;;335:50;;;381:1;378;371:12;335:50;418:4;410:6;406:17;394:29;;470:3;463:4;454:6;446;442:19;438:30;435:39;432:59;;;487:1;484;477:12;502:1205;628:6;636;644;652;660;668;676;684;737:3;725:9;716:7;712:23;708:33;705:53;;;754:1;751;744:12;705:53;793:9;780:23;812:31;837:5;812:31;:::i;:::-;862:5;-1:-1:-1;919:2:1;904:18;;891:32;932:33;891:32;932:33;:::i;:::-;984:7;-1:-1:-1;1043:2:1;1028:18;;1015:32;1056:33;1015:32;1056:33;:::i;:::-;1108:7;-1:-1:-1;1162:2:1;1147:18;;1134:32;;-1:-1:-1;1217:3:1;1202:19;;1189:33;1241:18;1271:14;;;1268:34;;;1298:1;1295;1288:12;1268:34;1337:58;1387:7;1378:6;1367:9;1363:22;1337:58;:::i;:::-;1414:8;;-1:-1:-1;1311:84:1;-1:-1:-1;1502:3:1;1487:19;;1474:33;;-1:-1:-1;1519:16:1;;;1516:36;;;1548:1;1545;1538:12;1516:36;;1587:60;1639:7;1628:8;1617:9;1613:24;1587:60;:::i;:::-;502:1205;;;;-1:-1:-1;502:1205:1;;-1:-1:-1;502:1205:1;;;;;;1666:8;-1:-1:-1;;;502:1205:1:o;1712:201::-;1801:20;;-1:-1:-1;;;;;1850:38:1;;1840:49;;1830:77;;1903:1;1900;1893:12;1830:77;1712:201;;;:::o;1918:511::-;2023:6;2031;2039;2092:2;2080:9;2071:7;2067:23;2063:32;2060:52;;;2108:1;2105;2098:12;2060:52;2147:9;2134:23;2166:31;2191:5;2166:31;:::i;:::-;2216:5;-1:-1:-1;2273:2:1;2258:18;;2245:32;2286:33;2245:32;2286:33;:::i;:::-;2338:7;-1:-1:-1;2364:59:1;2419:2;2404:18;;2364:59;:::i;:::-;2354:69;;1918:511;;;;;:::o;2434:165::-;2503:5;2548:3;2539:6;2534:3;2530:16;2526:26;2523:46;;;2565:1;2562;2555:12;2523:46;-1:-1:-1;2587:6:1;2434:165;-1:-1:-1;2434:165:1:o;2604:162::-;2670:5;2715:3;2706:6;2701:3;2697:16;2693:26;2690:46;;;2732:1;2729;2722:12;2771:531;2917:6;2925;2933;2986:3;2974:9;2965:7;2961:23;2957:33;2954:53;;;3003:1;3000;2993:12;2954:53;3042:9;3029:23;3061:31;3086:5;3061:31;:::i;:::-;3111:5;-1:-1:-1;3135:72:1;3199:7;3194:2;3179:18;;3135:72;:::i;:::-;3125:82;;3226:70;3288:7;3282:3;3271:9;3267:19;3226:70;:::i;3307:247::-;3366:6;3419:2;3407:9;3398:7;3394:23;3390:32;3387:52;;;3435:1;3432;3425:12;3387:52;3474:9;3461:23;3493:31;3518:5;3493:31;:::i;:::-;3543:5;3307:247;-1:-1:-1;;;3307:247:1:o;3559:257::-;3654:6;3707:3;3695:9;3686:7;3682:23;3678:33;3675:53;;;3724:1;3721;3714:12;3675:53;3747:63;3802:7;3791:9;3747:63;:::i;4003:315::-;4071:6;4079;4132:2;4120:9;4111:7;4107:23;4103:32;4100:52;;;4148:1;4145;4138:12;4100:52;4184:9;4171:23;4161:33;;4244:2;4233:9;4229:18;4216:32;4257:31;4282:5;4257:31;:::i;:::-;4307:5;4297:15;;;4003:315;;;;;:::o;4747:388::-;4815:6;4823;4876:2;4864:9;4855:7;4851:23;4847:32;4844:52;;;4892:1;4889;4882:12;4844:52;4931:9;4918:23;4950:31;4975:5;4950:31;:::i;:::-;5000:5;-1:-1:-1;5057:2:1;5042:18;;5029:32;5070:33;5029:32;5070:33;:::i;5140:180::-;5199:6;5252:2;5240:9;5231:7;5227:23;5223:32;5220:52;;;5268:1;5265;5258:12;5220:52;-1:-1:-1;5291:23:1;;5140:180;-1:-1:-1;5140:180:1:o;5325:127::-;5386:10;5381:3;5377:20;5374:1;5367:31;5417:4;5414:1;5407:15;5441:4;5438:1;5431:15;5457:837;-1:-1:-1;;;;;5869:39:1;;5851:58;;5957:14;5945:27;;5940:2;5925:18;;5918:55;6021:10;6009:23;;6004:2;5989:18;;5982:51;6081:8;6069:21;;6064:2;6049:18;;6042:49;5838:3;5823:19;;6121:1;6110:13;;6100:144;;6166:10;6161:3;6157:20;6154:1;6147:31;6201:4;6198:1;6191:15;6229:4;6226:1;6219:15;6100:144;6281:6;6275:3;6264:9;6260:19;6253:35;5457:837;;;;;;;;:::o;6527:464::-;6673:6;6681;6689;6742:3;6730:9;6721:7;6717:23;6713:33;6710:53;;;6759:1;6756;6749:12;6710:53;6795:9;6782:23;6772:33;;6824:72;6888:7;6883:2;6872:9;6868:18;6824:72;:::i;7188:626::-;7285:6;7293;7346:2;7334:9;7325:7;7321:23;7317:32;7314:52;;;7362:1;7359;7352:12;7314:52;7402:9;7389:23;7431:18;7472:2;7464:6;7461:14;7458:34;;;7488:1;7485;7478:12;7458:34;7526:6;7515:9;7511:22;7501:32;;7571:7;7564:4;7560:2;7556:13;7552:27;7542:55;;7593:1;7590;7583:12;7542:55;7633:2;7620:16;7659:2;7651:6;7648:14;7645:34;;;7675:1;7672;7665:12;7645:34;7728:7;7723:2;7713:6;7710:1;7706:14;7702:2;7698:23;7694:32;7691:45;7688:65;;;7749:1;7746;7739:12;7688:65;7780:2;7772:11;;;;;7802:6;;-1:-1:-1;7188:626:1;;-1:-1:-1;;;;7188:626:1:o;7819:250::-;7904:1;7914:113;7928:6;7925:1;7922:13;7914:113;;;8004:11;;;7998:18;7985:11;;;7978:39;7950:2;7943:10;7914:113;;;-1:-1:-1;;8061:1:1;8043:16;;8036:27;7819:250::o;8074:270::-;8115:3;8153:5;8147:12;8180:6;8175:3;8168:19;8196:76;8265:6;8258:4;8253:3;8249:14;8242:4;8235:5;8231:16;8196:76;:::i;:::-;8326:2;8305:15;-1:-1:-1;;8301:29:1;8292:39;;;;8333:4;8288:50;;8074:270;-1:-1:-1;;8074:270:1:o;8349:800::-;8509:4;8538:2;8578;8567:9;8563:18;8608:2;8597:9;8590:21;8631:6;8666;8660:13;8697:6;8689;8682:22;8735:2;8724:9;8720:18;8713:25;;8797:2;8787:6;8784:1;8780:14;8769:9;8765:30;8761:39;8747:53;;8835:2;8827:6;8823:15;8856:1;8866:254;8880:6;8877:1;8874:13;8866:254;;;8973:2;8969:7;8957:9;8949:6;8945:22;8941:36;8936:3;8929:49;9001:39;9033:6;9024;9018:13;9001:39;:::i;:::-;8991:49;-1:-1:-1;9098:12:1;;;;9063:15;;;;8902:1;8895:9;8866:254;;;-1:-1:-1;9137:6:1;;8349:800;-1:-1:-1;;;;;;;8349:800:1:o;9154:219::-;9303:2;9292:9;9285:21;9266:4;9323:44;9363:2;9352:9;9348:18;9340:6;9323:44;:::i;9600:370::-;9696:6;9704;9757:2;9745:9;9736:7;9732:23;9728:32;9725:52;;;9773:1;9770;9763:12;9725:52;9812:9;9799:23;9831:31;9856:5;9831:31;:::i;:::-;9881:5;-1:-1:-1;9905:59:1;9960:2;9945:18;;9905:59;:::i;:::-;9895:69;;9600:370;;;;;:::o;9975:396::-;10112:6;10120;10173:3;10161:9;10152:7;10148:23;10144:33;10141:53;;;10190:1;10187;10180:12;10141:53;10213:63;10268:7;10257:9;10213:63;:::i;:::-;10203:73;;10295:70;10357:7;10351:3;10340:9;10336:19;10295:70;:::i;10584:251::-;10654:6;10707:2;10695:9;10686:7;10682:23;10678:32;10675:52;;;10723:1;10720;10713:12;10675:52;10755:9;10749:16;10774:31;10799:5;10774:31;:::i;10840:127::-;10901:10;10896:3;10892:20;10889:1;10882:31;10932:4;10929:1;10922:15;10956:4;10953:1;10946:15;10972:125;11037:9;;;11058:10;;;11055:36;;;11071:18;;:::i;11514:277::-;11581:6;11634:2;11622:9;11613:7;11609:23;11605:32;11602:52;;;11650:1;11647;11640:12;11602:52;11682:9;11676:16;11735:5;11728:13;11721:21;11714:5;11711:32;11701:60;;11757:1;11754;11747:12;11796:311;11883:6;11936:2;11924:9;11915:7;11911:23;11907:32;11904:52;;;11952:1;11949;11942:12;11904:52;11991:9;11978:23;12041:16;12034:5;12030:28;12023:5;12020:39;12010:67;;12073:1;12070;12063:12;12112:308;12204:6;12257:2;12245:9;12236:7;12232:23;12228:32;12225:52;;;12273:1;12270;12263:12;12225:52;12312:9;12299:23;12362:8;12355:5;12351:20;12344:5;12341:31;12331:59;;12386:1;12383;12376:12;12425:315;12522:6;12575:2;12563:9;12554:7;12550:23;12546:32;12543:52;;;12591:1;12588;12581:12;12543:52;12630:9;12617:23;12680:10;12673:5;12669:22;12662:5;12659:33;12649:61;;12706:1;12703;12696:12;12745:313;12836:6;12889:2;12877:9;12868:7;12864:23;12860:32;12857:52;;;12905:1;12902;12895:12;12857:52;12944:9;12931:23;12994:14;12987:5;12983:26;12976:5;12973:37;12963:65;;13024:1;13021;13014:12;13063:235;13150:6;13203:2;13191:9;13182:7;13178:23;13174:32;13171:52;;;13219:1;13216;13209:12;13171:52;13242:50;13282:9;13242:50;:::i;15380:127::-;15441:10;15436:3;15432:20;15429:1;15422:31;15472:4;15469:1;15462:15;15496:4;15493:1;15486:15;15512:127;15573:10;15568:3;15564:20;15561:1;15554:31;15604:4;15601:1;15594:15;15628:4;15625:1;15618:15;15644:521;15721:4;15727:6;15787:11;15774:25;15881:2;15877:7;15866:8;15850:14;15846:29;15842:43;15822:18;15818:68;15808:96;;15900:1;15897;15890:12;15808:96;15927:33;;15979:20;;;-1:-1:-1;16022:18:1;16011:30;;16008:50;;;16054:1;16051;16044:12;16008:50;16087:4;16075:17;;-1:-1:-1;16118:14:1;16114:27;;;16104:38;;16101:58;;;16155:1;16152;16145:12;16170:135;16209:3;16230:17;;;16227:43;;16250:18;;:::i;:::-;-1:-1:-1;16297:1:1;16286:13;;16170:135::o;17770:188::-;-1:-1:-1;;;;;17883:10:1;;;17895;;;17879:27;;17918:11;;;17915:37;;;17932:18;;:::i;17963:170::-;18030:8;18058:10;;;18070;;;18054:27;;18093:11;;;18090:37;;;18107:18;;:::i;18541:392::-;18753:19;;;18797:2;18788:12;;18781:28;;;;18865:3;18843:16;-1:-1:-1;;;;;;18839:59:1;18834:2;18825:12;;18818:81;18924:2;18915:12;;18541:392::o;19896:1033::-;19986:6;20017:3;20061:2;20049:9;20040:7;20036:23;20032:32;20029:52;;;20077:1;20074;20067:12;20029:52;20110:2;20104:9;20140:15;;;;20185:18;20170:34;;20206:22;;;20167:62;20164:185;;;20271:10;20266:3;20262:20;20259:1;20252:31;20306:4;20303:1;20296:15;20334:4;20331:1;20324:15;20164:185;20369:10;20365:2;20358:22;20417:9;20404:23;20396:6;20389:39;20489:2;20478:9;20474:18;20461:32;20456:2;20448:6;20444:15;20437:57;20555:2;20544:9;20540:18;20527:32;20522:2;20514:6;20510:15;20503:57;20621:2;20610:9;20606:18;20593:32;20588:2;20580:6;20576:15;20569:57;20688:3;20677:9;20673:19;20660:33;20654:3;20646:6;20642:16;20635:59;20756:3;20745:9;20741:19;20728:33;20722:3;20714:6;20710:16;20703:59;20824:3;20813:9;20809:19;20796:33;20790:3;20782:6;20778:16;20771:59;20892:3;20881:9;20877:19;20864:33;20858:3;20850:6;20846:16;20839:59;20917:6;20907:16;;;;19896:1033;;;;:::o;20934:176::-;21001:14;21035:10;;;21047;;;21031:27;;21070:11;;;21067:37;;;21084:18;;:::i;21115:191::-;-1:-1:-1;;;;;21242:10:1;;;21230;;;21226:27;;21265:12;;;21262:38;;;21280:18;;:::i;22245:412::-;22447:2;22429:21;;;22486:2;22466:18;;;22459:30;22525:34;22520:2;22505:18;;22498:62;-1:-1:-1;;;22591:2:1;22576:18;;22569:46;22647:3;22632:19;;22245:412::o;24143:172::-;24210:10;24240;;;24252;;;24236:27;;24275:11;;;24272:37;;;24289:18;;:::i;25420:128::-;25487:9;;;25508:11;;;25505:37;;;25522:18;;:::i;26115:287::-;26244:3;26282:6;26276:13;26298:66;26357:6;26352:3;26345:4;26337:6;26333:17;26298:66;:::i;:::-;26380:16;;;;;26115:287;-1:-1:-1;;26115:287:1:o;26407:209::-;26439:1;26465;26455:132;;26509:10;26504:3;26500:20;26497:1;26490:31;26544:4;26541:1;26534:15;26572:4;26569:1;26562:15;26455:132;-1:-1:-1;26601:9:1;;26407:209::o

Swarm Source

ipfs://657b679d135fc011c665beb7b96a5c08d8e786ec0e285b7486f2829d5e9e2ade

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
Loading...
Loading

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.