Contract Overview
My Name Tag:
Not Available, login to update
[ Download CSV Export ]
Contract Name:
EIP712Coordinator
Compiler Version
v0.8.19+commit.7dd6d404
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.4; import {ECDSA} from "solady/utils/ECDSA.sol"; import {Coordinator} from "./Coordinator.sol"; import {EIP712} from "solady/utils/EIP712.sol"; import {Delegator} from "./pattern/Delegator.sol"; /// @title EIP712Coordinator /// @notice Coordinator enhanced with ability to created subscriptions via off-chain EIP-712 signature /// @dev Allows creating a subscription on behalf of a contract via delegatee EOA signature /// @dev Allows nodes to atomically create subscriptions and deliver compute responses contract EIP712Coordinator is EIP712, Coordinator { /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ /// @notice EIP-712 signing domain major version string public constant EIP712_VERSION = "1"; /// @notice EIP-712 signing domain name string public constant EIP712_NAME = "InfernetCoordinator"; /// @notice Gas overhead in wei to retrieve cached subscriptionId for existing delegatee-created subscription /// @dev A uint16 is sufficient but increases control plane costs. While we can pack this and the subsequent uint24 /// in contract storage to save data plane costs, we prioritize control plane and instead simply use a uint256 uint256 public constant DELEGATEE_OVERHEAD_CACHED_WEI = 600 wei; /// @notice Gas overhead in wei to create a new subscription via delegatee signature /// @dev Make note that this does not account for gas costs of dynamic inputs (containerId, inputs), just base overhead /// @dev Can fit within uint24, see comment for `DELEGATEE_OVERHEAD_CACHED_WEI` for details uint256 public constant DELEGATEE_OVERHEAD_CREATE_WEI = 91_200 wei; /// @notice EIP-712 struct(Subscription) typeHash bytes32 private constant EIP712_SUBSCRIPTION_TYPEHASH = keccak256( "Subscription(address owner,uint32 activeAt,uint32 period,uint32 frequency,uint16 redundancy,uint48 maxGasPrice,uint32 maxGasLimit,string containerId,bytes inputs)" ); /// @notice EIP-712 struct(DelegateSubscription) typeHash /// @dev struct(DelegateSubscription) == { uint32 nonce, uint32 expiry, Subscription sub } /// @dev The `nonce` represents the nonce of the subscribing contract (sub-owner); prevents signature replay /// @dev The `expiry` is when the delegated subscription signature expires and can no longer be used bytes32 private constant EIP712_DELEGATE_SUBSCRIPTION_TYPEHASH = keccak256( "DelegateSubscription(uint32 nonce,uint32 expiry,Subscription sub)Subscription(address owner,uint32 activeAt,uint32 period,uint32 frequency,uint16 redundancy,uint48 maxGasPrice,uint32 maxGasLimit,string containerId,bytes inputs)" ); /*////////////////////////////////////////////////////////////// MUTABLE //////////////////////////////////////////////////////////////*/ /// @notice Subscribing contract => maximum seen nonce /// @dev The nonce is a uint32 size(4.2B) which would take > 100 years of incrementing nonce per second to overflow mapping(address => uint32) public maxSubscriberNonce; /// @notice hash(subscribing contract, nonce) => subscriptionId /// @notice Allows lookup between a delegated subscription creation (unique(subscriber, nonce)) and subscriptionId mapping(bytes32 => uint32) public delegateCreatedIds; /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown by `createSubscriptionDelegatee()` if subscription signature does not match contract delegatee /// @dev 4-byte signature: `0x10c74b03` error SignerMismatch(); /// @notice Thrown by `createSubscriptionDelegatee()` if signature for delegated subscription has expired /// @dev 4-byte signature: `0x0819bdcd` error SignatureExpired(); /*////////////////////////////////////////////////////////////// OVERRIDE FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Overrides Solady.EIP712._domainNameAndVersion to return EIP712-compatible domain name, version function _domainNameAndVersion() internal pure override returns (string memory, string memory) { return (EIP712_NAME, EIP712_VERSION); } /// @notice Overrides Solady.EIP712._domainNameAndVersionMayChange to always return false since the domain params are not updateable function _domainNameAndVersionMayChange() internal pure override returns (bool) { return false; } /*////////////////////////////////////////////////////////////// FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Allows a delegatee to create a subscription on behalf of a subscribing contract (sub.owner) /// @dev Unlike `Coordinator.createSubscription()`, offers maximum flexibility to set subscription parameters /// @param nonce subscribing contract nonce (included in signature) /// @param expiry delegated subscription signature expiry (included in signature) /// @param sub subscription to create /// @param v ECDSA recovery id /// @param r ECDSA signature output (r) /// @param s ECDSA signature output (s) /// @return 0: subscriptionId (if subscription exists, returns existing ID, else returns new ID), /// 1: exists (true if returning existing subscription, else false) function createSubscriptionDelegatee( uint32 nonce, uint32 expiry, Subscription calldata sub, uint8 v, bytes32 r, bytes32 s ) public returns (uint32, bool) { // Check if subscription already exists via delegate-created lookup table bytes32 key = keccak256(abi.encode(sub.owner, nonce)); uint32 subscriptionId = delegateCreatedIds[key]; // If subscription exists, return existing subscriptionId // This implicitly prevents nonce replay because if the nonce was already used, a subscription would exist if (subscriptionId != 0) { return (subscriptionId, true); } // Else, if subscription does not exist // First, verify that signature has not expired if (uint32(block.timestamp) >= expiry) { revert SignatureExpired(); } // Generate EIP-712 data bytes32 digest = _hashTypedData( keccak256( // Encode(DelegateSubscription, nonce, expiry, sub) abi.encode( EIP712_DELEGATE_SUBSCRIPTION_TYPEHASH, nonce, expiry, // Encode(Subscription, sub) keccak256( abi.encode( EIP712_SUBSCRIPTION_TYPEHASH, sub.owner, sub.activeAt, sub.period, sub.frequency, sub.redundancy, sub.maxGasPrice, sub.maxGasLimit, // Hash dynamic values keccak256(bytes(sub.containerId)), keccak256(sub.inputs) ) ) ) ) ); // Get recovered signer from data // Throws `InvalidSignature()` (4-byte signature: `0x8baa579f`) if can't recover signer address recoveredSigner = ECDSA.recover(digest, v, r, s); // Collect delegated signer from subscribing contract address delegatedSigner = Delegator(sub.owner).signer(); // Verify signatures (recoveredSigner should equal delegatedSigner) if (recoveredSigner != delegatedSigner) { revert SignerMismatch(); } // By this point, the signer is verified and a net-new subscription can be created // Assign new subscription id // Unlikely this will ever overflow so we can toss in unchecked unchecked { subscriptionId = id++; } // Store provided subscription as-is subscriptions[subscriptionId] = sub; // Update delegate-created ID lookup table delegateCreatedIds[key] = subscriptionId; // Emit new subscription emit SubscriptionCreated(subscriptionId); // Update max known subscriber nonce (useful for off-chain signing utilities to prevent nonce-collision) if (nonce > maxSubscriberNonce[sub.owner]) { maxSubscriberNonce[sub.owner] = nonce; } // Explicitly return subscriptionId return (subscriptionId, false); } /// @notice Allows active nodes to (1) atomically create or collect subscription via signed EIP-712 message, /// (2) deliver container compute responses for created or collected subscription /// @param nonce subscribing contract nonce (included in signature) /// @param expiry delegated subscription signature expiry (included in signature) /// @param sub subscription to create /// @param v ECDSA recovery id /// @param r ECDSA signature output (r) /// @param s ECDSA signature output (s) /// @param deliveryInterval subscription `interval` /// @param input optional off-chain input recorded by Infernet node (empty, hashed input, processed input, or both) /// @param output optional off-chain container output (empty, hashed output, processed output, both, or fallback: all encodeable data) /// @param proof optional container execution proof (or arbitrary metadata) function deliverComputeDelegatee( uint32 nonce, uint32 expiry, Subscription calldata sub, uint8 v, bytes32 r, bytes32 s, uint32 deliveryInterval, bytes calldata input, bytes calldata output, bytes calldata proof ) external onlyActiveNode { // Create subscriptionId via delegatee creation + or collect if subscription already exists (uint32 subscriptionId, bool cached) = createSubscriptionDelegatee(nonce, expiry, sub, v, r, s); // Calculate additional gas overhead imposed from delivering container compute response via delegatee function uint256 overhead; if (cached) { // Subscription exists, cost to retrieve subscriptionId overhead = DELEGATEE_OVERHEAD_CACHED_WEI; } else { // Subscription does not exist, cost to create subscription w/ delegatee signature overhead = DELEGATEE_OVERHEAD_CREATE_WEI; } // Deliver subscription response _deliverComputeWithOverhead(subscriptionId, deliveryInterval, input, output, proof, overhead); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Gas optimized ECDSA wrapper. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) /// /// @dev Note: /// - The recovery functions use the ecrecover precompile (0x1). /// /// WARNING! Do NOT use signatures as unique identifiers. /// Please use EIP712 with a nonce included in the digest to prevent replay attacks. /// This implementation does NOT check if a signature is non-malleable. library ECDSA { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The signature is invalid. error InvalidSignature(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RECOVERY OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // Note: as of Solady version 0.0.68, these functions will // revert upon recovery failure for more safety by default. /// @dev Recovers the signer's address from a message digest `hash`, /// and the `signature`. /// /// This function does NOT accept EIP-2098 short form signatures. /// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098 /// short form signatures instead. function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. let signatureLength := mload(signature) mstore(0x00, hash) mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x40, mload(add(signature, 0x20))) // `r`. mstore(0x60, mload(add(signature, 0x40))) // `s`. result := mload( staticcall( gas(), // Amount of gas left for the transaction. eq(signatureLength, 65), // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the `signature`. /// /// This function does NOT accept EIP-2098 short form signatures. /// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098 /// short form signatures instead. function recoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. result := mload( staticcall( gas(), // Amount of gas left for the transaction. eq(signature.length, 65), // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the EIP-2098 short form signature defined by `r` and `vs`. /// /// This function only accepts EIP-2098 short form signatures. /// See: https://eips.ethereum.org/EIPS/eip-2098 function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) mstore(0x60, shr(1, shl(1, vs))) // `s`. result := mload( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the signature defined by `v`, `r`, `s`. function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) mstore(0x40, r) mstore(0x60, s) result := mload( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(returndatasize()) { mstore(0x00, 0x8baa579f) // `InvalidSignature()`. revert(0x1c, 0x04) } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* TRY-RECOVER OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // WARNING! // These functions will NOT revert upon recovery failure. // Instead, they will return the zero address upon recovery failure. // It is critical that the returned address is NEVER compared against // a zero address (e.g. an uninitialized address variable). /// @dev Recovers the signer's address from a message digest `hash`, /// and the `signature`. /// /// This function does NOT accept EIP-2098 short form signatures. /// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098 /// short form signatures instead. function tryRecover(bytes32 hash, bytes memory signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. let signatureLength := mload(signature) mstore(0x00, hash) mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x40, mload(add(signature, 0x20))) // `r`. mstore(0x60, mload(add(signature, 0x40))) // `s`. pop( staticcall( gas(), // Amount of gas left for the transaction. eq(signatureLength, 65), // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the `signature`. /// /// This function does NOT accept EIP-2098 short form signatures. /// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098 /// short form signatures instead. function tryRecoverCalldata(bytes32 hash, bytes calldata signature) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. pop( staticcall( gas(), // Amount of gas left for the transaction. eq(signature.length, 65), // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the EIP-2098 short form signature defined by `r` and `vs`. /// /// This function only accepts EIP-2098 short form signatures. /// See: https://eips.ethereum.org/EIPS/eip-2098 function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) mstore(0x60, shr(1, shl(1, vs))) // `s`. pop( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Recovers the signer's address from a message digest `hash`, /// and the signature defined by `v`, `r`, `s`. function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address result) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x00, hash) mstore(0x20, and(v, 0xff)) mstore(0x40, r) mstore(0x60, s) pop( staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x40, // Start of output. 0x20 // Size of output. ) ) mstore(0x60, 0) // Restore the zero slot. // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. result := mload(xor(0x60, returndatasize())) mstore(0x40, m) // Restore the free memory pointer. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HASHING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an Ethereum Signed Message, created from a `hash`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) /// JSON-RPC method as part of EIP-191. function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { mstore(0x20, hash) // Store into scratch space for keccak256. mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } } /// @dev Returns an Ethereum Signed Message, created from `s`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) /// JSON-RPC method as part of EIP-191. /// Note: Supports lengths of `s` up to 999999 bytes. function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let sLength := mload(s) let o := 0x20 mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. mstore(0x00, 0x00) // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. for { let temp := sLength } 1 {} { o := sub(o, 1) mstore8(o, add(48, mod(temp, 10))) temp := div(temp, 10) if iszero(temp) { break } } let n := sub(0x3a, o) // Header length: `26 + 32 - o`. // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) mstore(s, sLength) // Restore the length. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EMPTY CALLDATA HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an empty calldata bytes. function emptySignature() internal pure returns (bytes calldata signature) { /// @solidity memory-safe-assembly assembly { signature.length := 0 } } }
// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.4; import {Manager} from "./Manager.sol"; import {BaseConsumer} from "./consumer/Base.sol"; /// @title Coordinator /// @notice Coordination layer between consuming smart contracts and off-chain Infernet nodes /// @dev Allows creating and deleting `Subscription`(s) /// @dev Allows nodes with `Manager.NodeStatus.Active` to deliver subscription outputs via off-chain container compute contract Coordinator is Manager { /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ /// @notice A subscription is the fundamental unit of Infernet /// @dev A subscription represents some request configuration for off-chain compute via containers on Infernet nodes /// @dev A subscription with `frequency == 1` is a one-time subscription (a callback) /// @dev A subscription with `frequency > 1` is a recurring subscription (many callbacks) /// @dev Tightly-packed struct: /// - [owner, activeAt, period, frequency]: [32, 160, 32, 32] = 256 /// - [redundancy, maxGasPrice, maxGasLimit]: [16, 48, 32] = 96 struct Subscription { /// @notice Subscription owner + recipient /// @dev This is the address called to fulfill a subscription request and must inherit `BaseConsumer` /// @dev Default initializes to `address(0)` address owner; /// @notice Timestamp when subscription is first active and an off-chain Infernet node can respond /// @dev When `period == 0`, the subscription is immediately active /// @dev When `period > 0`, subscription is active at `createdAt + period` uint32 activeAt; /// @notice Time, in seconds, between each subscription interval /// @dev At worst, assuming subscription occurs once/year << uint32 uint32 period; /// @notice Number of times a subscription is processed /// @dev At worst, assuming 30 req/min * 60 min * 24 hours * 365 days * 10 years << uint32 uint32 frequency; /// @notice Number of unique nodes that can fulfill a subscription at each `interval` /// @dev uint16 allows for >255 nodes (uint8) but <65,535 uint16 redundancy; /// @notice Max gas price in wei paid by an Infernet node when fulfilling callback /// @dev uint40 caps out at ~1099 gwei, uint48 allows up to ~281K gwei uint48 maxGasPrice; /// @notice Max gas limit in wei used by an Infernet node when fulfilling callback /// @dev Must be at least equal to the gas limit of your receiving function execution + DELIVERY_OVERHEAD_WEI /// @dev uint24 is too small at ~16.7M (<30M mainnet gas limit), but uint32 is more than enough (~4.2B wei) uint32 maxGasLimit; /// @notice Container identifier used by off-chain Infernet nodes to determine which container is used to fulfill a subscription /// @dev Can be used to specify a linear DAG of containers by seperating container names with a "," delimiter ("A,B,C") /// @dev Better represented by a string[] type but constrained to string to keep struct and functions simple string containerId; /// @notice Optional container input parameters /// @dev If left empty, off-chain Infernet nodes call public view fn: `BaseConsumer(owner).getContainerInputs()` bytes inputs; } /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ /// @notice Gas overhead in wei to deliver container compute responses /// @dev This is the additional cost of any validation checks performed within the `Coordinator` /// before delivering responses to consumer contracts /// @dev A uint16 is sufficient but we are not packing variables so control plane cost is higher because of type /// casting during operations. Thus, we can just stick to uint256 uint256 public constant DELIVERY_OVERHEAD_WEI = 56_600 wei; /*////////////////////////////////////////////////////////////// MUTABLE //////////////////////////////////////////////////////////////*/ /// @notice Current highest subscription ID /// @dev 1-indexed to allow using id as a mapping value (prevent 0-indexed default from being misused) /// @dev uint32 size(4.2B) should be sufficiently large uint32 public id = 1; /// @notice hash(subscriptionId, interval, caller) => has caller responded for (sub, interval)? mapping(bytes32 => bool) public nodeResponded; /// @notice hash(subscriptionId, interval) => Number of responses for (sub, interval)? /// @dev Limited to type(Subscription.redundancy) == uint16 /// @dev Technically, this is not required and we can save an SLOAD if we simply add a uint48 to the subscription /// struct that represents 32 bits of the interval -> 16 bits of redundancy count, reset each interval change /// But, this is a little over the optimization:redability line and would make Subscriptions harder to grok mapping(bytes32 => uint16) public redundancyCount; /// @notice subscriptionID => Subscription /// @dev 1-indexed, 0th-subscription is empty mapping(uint32 => Subscription) public subscriptions; /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /// @notice Emitted when a new subscription is created /// @param id subscription ID event SubscriptionCreated(uint32 indexed id); /// @notice Emitted when a subscription is cancelled /// @param id subscription ID event SubscriptionCancelled(uint32 indexed id); /// @notice Emitted when a subscription is fulfilled /// @param id subscription ID /// @param node address of fulfilling node event SubscriptionFulfilled(uint32 indexed id, address indexed node); /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown by `deliverComputeWithOverhead()` if delivering tx with gasPrice > subscription maxGasPrice /// @dev E.g. submitting tx with gas price `10 gwei` when network basefee is `11 gwei` /// @dev 4-byte signature: `0x682bad5a` error GasPriceExceeded(); /// @notice Thrown by `deliverComputeWithOverhead()` if delivering tx with consumed gas > subscription maxGasLimit /// @dev E.g. submitting tx with gas consumed `200_000 wei` when max allowed by subscription is `175_000 wei` /// @dev 4-byte signature: `0xbe9179a6` error GasLimitExceeded(); /// @notice Thrown by `deliverComputeWithOverhead()` if attempting to deliver container compute response for non-current interval /// @dev E.g submitting tx for `interval` < current (period elapsed) or `interval` > current (too early to submit) /// @dev 4-byte signature: `0x4db310c3` error IntervalMismatch(); /// @notice Thrown by `deliverComputeWithOverhead()` if `redundancy` has been met for current `interval` /// @dev E.g submitting 4th output tx for a subscription with `redundancy == 3` /// @dev 4-byte signature: `0x2f4ca85b` error IntervalCompleted(); /// @notice Thrown by `deliverComputeWithOverhead()` if `node` has already responded this `interval` /// @dev 4-byte signature: `0x88a21e4f` error NodeRespondedAlready(); /// @notice Thrown by `deliverComputeWithOverhead()` if attempting to access a subscription that does not exist /// @dev 4-byte signature: `0x1a00354f` error SubscriptionNotFound(); /// @notice Thrown by `cancelSubscription()` if attempting to modify a subscription not owned by caller /// @dev 4-byte signature: `0xa7fba711` error NotSubscriptionOwner(); /// @notice Thrown by `deliverComputeWithOverhead()` if attempting to deliver a completed subscription /// @dev 4-byte signature: `0xae6704a7` error SubscriptionCompleted(); /// @notice Thrown by `deliverComputeWithOverhead()` if attempting to deliver a subscription before `activeAt` /// @dev 4-byte signature: `0xefb74efe` error SubscriptionNotActive(); /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Internal counterpart to `deliverCompute()` w/ ability to set custom gas overhead allowance /// @dev When called by `deliverCompute()`, `callingOverheadWei == 0` because no additional overhead imposed /// @dev When called by `deliverComputeDelegatee()`, `DELEGATEE_OVERHEAD_*_WEI` is imposed /// @param subscriptionId subscription ID to deliver /// @param deliveryInterval subscription `interval` to deliver /// @param input optional off-chain input recorded by Infernet node (empty, hashed input, processed input, or both) /// @param output optional off-chain container output (empty, hashed output, processed output, both, or fallback: all encodeable data) /// @param proof optional container execution proof (or arbitrary metadata) /// @param callingOverheadWei additional overhead gas used for delivery function _deliverComputeWithOverhead( uint32 subscriptionId, uint32 deliveryInterval, bytes calldata input, bytes calldata output, bytes calldata proof, uint256 callingOverheadWei ) internal { // Naively, one would think that loading a subscription into memory via // `Subscription memory subscription = subscriptions[subscriptionId]` // would be cost-effective and most readable. // Unfortunately, this is not the case. This function makes no use of // `subscription.containerId` or `subscription.inputs`. Because these // are dynamic types, we are forced to pay to load into memory the length // + content of these parameters. In some cases (say, container input being // 100 uint256's), we are forced to pay 2 SLOAD (length slot containerId, inputs) // + N SLOAD (containerId + inputs byte length / word size) (for example, 100 // SLOAD's in the case of 100 uint256's) + N MSTORE (copying into memory) // + memory expansion costs. // To avoid this, we can first access memory parameters selectively, copying // just the fixed size params (uint16, etc.) into memory by accessing state via // `subscriptions[subscriptionId].activeAt` syntax. // But, with this syntax, while we avoid the significant overhead of copying // from storage, into memory, the unnecessary dynamic parameters, we are now // forced to pay 100 gas for each non-first storage slot read (hot SLOAD). // For example, even if accessing two tightly-packed variables in slot 0, we must // pay COLD SLOAD + HOT SLOAD, rather than just COLD SLOAD + MLOAD. // To avoid this, we can drop down to assembly and: // 1. Manually SLOAD tightly-packed struct slots // 2. Unpack and MSTORE variables to avoid the hot SLOAD penalty since we // only copy from storage into memory once (rather than for each variable) // Setup parameters in first slot // Note, we could load these variables right before they are used but the MSTORE is cheap and this is cleaner address subOwner; uint32 subActiveAt; uint32 subPeriod; uint32 subFrequency; // Store slot identifier for subscriptions[subscriptionId][slot 0] bytes32 storageSlot; assembly ("memory-safe") { // Load address of free-memory pointer let m := mload(0x40) // Store subscription ID to first free slot // uint32 automatically consumes full word mstore(m, subscriptionId) // Store subscriptions mapping storage slot (4) to 32 byte (1 word) offset mstore(add(m, 0x20), 4) // At this point, memory layout [0 -> 0x20 == subscriptionId, 0x20 -> 0x40 == 4] // Calculate mapping storage slot — hash(key, mapping slot) // Hash data from 0 -> 0x40 (2 words) storageSlot := keccak256(m, 0x40) // SLOAD struct data let data := sload(storageSlot) // Solidity packs structs right to left (least-significant bits a la little-endian) // MSTORE'ing tightly-packed variables from storage slot data // Erase first 96 bits via AND, grab last 160 subOwner := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) // Grab first 32 bits preceeding owner subActiveAt := and(shr(160, data), 0xFFFFFFFF) // Grab first 32 bits preceeding activeAt subPeriod := and(shr(192, data), 0xFFFFFFFF) // Grab first 32 bits from left subFrequency := shr(224, data) } // Revert if subscription does not exist if (subOwner == address(0)) { revert SubscriptionNotFound(); } // Revert if subscription is not yet active if (block.timestamp < subActiveAt) { revert SubscriptionNotActive(); } // Calculate subscription interval uint32 interval = getSubscriptionInterval(subActiveAt, subPeriod); // Revert if not processing curent interval if (interval != deliveryInterval) { revert IntervalMismatch(); } // Revert if interval > frequency if (interval > subFrequency) { revert SubscriptionCompleted(); } // Setup parameters in second slot uint16 subRedundancy; uint48 subMaxGasPrice; uint32 subMaxGasLimit; assembly ("memory-safe") { // SLOAD struct data // Second slot is simply offset from first by 1 let data := sload(add(storageSlot, 1)) // MSTORE'ing tightly-packed variables from storage slot data // Grab last 16 bits subRedundancy := and(data, 0xFFFF) // Grab first 48 bits preceeding redundancy subMaxGasPrice := and(shr(16, data), 0xFFFFFFFFFFFF) // Grab first 32 bits from left subMaxGasLimit := and(shr(64, data), 0xFFFFFFFF) } // Revert if tx gas price > max subscription allowed if (tx.gasprice > subMaxGasPrice) { revert GasPriceExceeded(); } // Revert if redundancy requirements for this interval have been met bytes32 key = keccak256(abi.encode(subscriptionId, interval)); uint16 numRedundantDeliveries = redundancyCount[key]; if (numRedundantDeliveries == subRedundancy) { revert IntervalCompleted(); } // Highly unlikely to overflow given incrementing by 1/node unchecked { redundancyCount[key] = numRedundantDeliveries + 1; } // Revert if node has already responded this interval key = keccak256(abi.encode(subscriptionId, interval, msg.sender)); if (nodeResponded[key]) { revert NodeRespondedAlready(); } nodeResponded[key] = true; // Deliver container compute output to contract (measuring execution cost) uint256 startingGas = gasleft(); BaseConsumer(subOwner).rawReceiveCompute( subscriptionId, interval, numRedundantDeliveries + 1, msg.sender, input, output, proof ); uint256 endingGas = gasleft(); // Revert if gas used > allowed, we can make unchecked: // Gas limit in most networks is usually much below uint256 max, and by this point a decent amount is spent // `callingOverheadWei`, `DELIVERY_OVERHEAD_WEI` both fit in under uint24's // Thus, this operation is unlikely to ever overflow ((uint256 - uint256) + (uint16 + uint24)) // Unless the bounds are along the lines of: {startingGas: UINT256_MAX, endingGas: << (callingOverheadWei + DELIVERY_OVERHEAD_WEI)} uint256 executionCost; unchecked { executionCost = startingGas - endingGas + callingOverheadWei + DELIVERY_OVERHEAD_WEI; } if (executionCost > subMaxGasLimit) { revert GasLimitExceeded(); } // Emit successful delivery emit SubscriptionFulfilled(subscriptionId, msg.sender); } /*////////////////////////////////////////////////////////////// FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Creates new subscription /// @param containerId compute container identifier used by off-chain Infernet node /// @param inputs optional container inputs /// @param maxGasPrice max gas price in wei paid by an Infernet node when fulfilling callback /// @param maxGasLimit max gas limit in wei paid by an Infernet node in callback tx /// @param frequency max number of times to process subscription (i.e, `frequency == 1` is a one-time request) /// @param period period, in seconds, at which to progress each responding `interval` /// @param redundancy number of unique responding Infernet nodes /// @return subscription ID function createSubscription( string memory containerId, bytes calldata inputs, uint48 maxGasPrice, uint32 maxGasLimit, uint32 frequency, uint32 period, uint16 redundancy ) external returns (uint32) { // Get subscription id and increment // Unlikely this will ever overflow so we can toss in unchecked uint32 subscriptionId; unchecked { subscriptionId = id++; } // Store new subscription subscriptions[subscriptionId] = Subscription({ // If period is = 0 (one-time), active immediately // Else, next active at first period mark // Probably reasonable to keep the overflow protection here given adding 2 uint32's into a uint32 activeAt: uint32(block.timestamp) + period, owner: msg.sender, maxGasPrice: maxGasPrice, redundancy: redundancy, maxGasLimit: maxGasLimit, frequency: frequency, period: period, containerId: containerId, inputs: inputs }); // Emit new subscription emit SubscriptionCreated(subscriptionId); // Explicitly return subscriptionId return subscriptionId; } /// @notice Cancel a subscription /// @dev Must be called by `subscriptions[subscriptionId].owner` /// @param subscriptionId subscription ID to cancel function cancelSubscription(uint32 subscriptionId) external { // Throw if owner of subscription is not caller if (subscriptions[subscriptionId].owner != msg.sender) { revert NotSubscriptionOwner(); } // Nullify subscription delete subscriptions[subscriptionId]; // Emit cancellation emit SubscriptionCancelled(subscriptionId); } /// @notice Calculates subscription `interval` based on `activeAt` and `period` /// @param activeAt when does a subscription start accepting callback responses /// @param period time, in seconds, between each subscription response `interval` /// @return current subscription interval function getSubscriptionInterval(uint32 activeAt, uint32 period) public view returns (uint32) { // If period is 0, we're always at interval 1 if (period == 0) { return 1; } // Else, interval = ((block.timestamp - activeAt) / period) + 1 // This is only called after validating block.timestamp >= activeAt so timestamp can't underflow // We also short-circuit above if period is zero so no need for division by zero checks unchecked { return ((uint32(block.timestamp) - activeAt) / period) + 1; } } /// @notice Allows nodes with `Manager.NodeStatus.Active` to deliver container compute responses for a subscription /// @dev Re-entering does not work because only active nodes (max 1 response) can call `deliverCompute` /// @dev Re-entering and delivering via a seperate node `msg.sender` works but is ignored in favor of explicit `maxGasLimit` /// @dev For containers without succinctly-verifiable proofs, the `proof` field can be repurposed for arbitrary metadata /// @dev Enforces an overhead delivery cost of `DELIVERY_OVERHEAD_WEI` and `0` additional overhead /// @param subscriptionId subscription ID to deliver /// @param deliveryInterval subscription `interval` to deliver /// @param input optional off-chain container input recorded by Infernet node (empty, hashed input, processed input, or both) /// @param output optional off-chain container output (empty, hashed output, processed output, both, or fallback: all encodeable data) /// @param proof optional off-chain container execution proof (or arbitrary metadata) function deliverCompute( uint32 subscriptionId, uint32 deliveryInterval, bytes calldata input, bytes calldata output, bytes calldata proof ) external onlyActiveNode { _deliverComputeWithOverhead(subscriptionId, deliveryInterval, input, output, proof, 0); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Contract for EIP-712 typed structured data hashing and signing. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol) /// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol) /// /// @dev Note, this implementation: /// - Uses `address(this)` for the `verifyingContract` field. /// - Does NOT use the optional EIP-712 salt. /// - Does NOT use any EIP-712 extensions. /// This is for simplicity and to save gas. /// If you need to customize, please fork / modify accordingly. abstract contract EIP712 { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS AND IMMUTABLES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; address private immutable _cachedThis; uint256 private immutable _cachedChainId; bytes32 private immutable _cachedNameHash; bytes32 private immutable _cachedVersionHash; bytes32 private immutable _cachedDomainSeparator; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTRUCTOR */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Cache the hashes for cheaper runtime gas costs. /// In the case of upgradeable contracts (i.e. proxies), /// or if the chain id changes due to a hard fork, /// the domain separator will be seamlessly calculated on-the-fly. constructor() { _cachedThis = address(this); _cachedChainId = block.chainid; string memory name; string memory version; if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion(); bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name)); bytes32 versionHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version)); _cachedNameHash = nameHash; _cachedVersionHash = versionHash; bytes32 separator; if (!_domainNameAndVersionMayChange()) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Load the free memory pointer. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) separator := keccak256(m, 0xa0) } } _cachedDomainSeparator = separator; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* FUNCTIONS TO OVERRIDE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Please override this function to return the domain name and version. /// ``` /// function _domainNameAndVersion() /// internal /// pure /// virtual /// returns (string memory name, string memory version) /// { /// name = "Solady"; /// version = "1"; /// } /// ``` /// /// Note: If the returned result may change after the contract has been deployed, /// you must override `_domainNameAndVersionMayChange()` to return true. function _domainNameAndVersion() internal view virtual returns (string memory name, string memory version); /// @dev Returns if `_domainNameAndVersion()` may change /// after the contract has been deployed (i.e. after the constructor). /// Default: false. function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {} /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HASHING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the EIP-712 domain separator. function _domainSeparator() internal view virtual returns (bytes32 separator) { if (_domainNameAndVersionMayChange()) { separator = _buildDomainSeparator(); } else { separator = _cachedDomainSeparator; if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); } } /// @dev Returns the hash of the fully encoded EIP-712 message for this domain, /// given `structHash`, as defined in /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct. /// /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message: /// ``` /// bytes32 digest = _hashTypedData(keccak256(abi.encode( /// keccak256("Mail(address to,string contents)"), /// mailTo, /// keccak256(bytes(mailContents)) /// ))); /// address signer = ECDSA.recover(digest, signature); /// ``` function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) { bytes32 separator; if (_domainNameAndVersionMayChange()) { separator = _buildDomainSeparator(); } else { separator = _cachedDomainSeparator; if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); } /// @solidity memory-safe-assembly assembly { // Compute the digest. mstore(0x00, 0x1901000000000000) // Store "\x19\x01". mstore(0x1a, separator) // Store the domain separator. mstore(0x3a, structHash) // Store the struct hash. digest := keccak256(0x18, 0x42) // Restore the part of the free memory slot that was overwritten. mstore(0x3a, 0) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EIP-5267 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev See: https://eips.ethereum.org/EIPS/eip-5267 function eip712Domain() public view virtual returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ) { fields = hex"0f"; // `0b01111`. (name, version) = _domainNameAndVersion(); chainId = block.chainid; verifyingContract = address(this); salt = salt; // `bytes32(0)`. extensions = extensions; // `new uint256[](0)`. } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the EIP-712 domain separator. function _buildDomainSeparator() private view returns (bytes32 separator) { bytes32 nameHash; bytes32 versionHash; if (_domainNameAndVersionMayChange()) { (string memory name, string memory version) = _domainNameAndVersion(); nameHash = keccak256(bytes(name)); versionHash = keccak256(bytes(version)); } else { nameHash = _cachedNameHash; versionHash = _cachedVersionHash; } /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Load the free memory pointer. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) separator := keccak256(m, 0xa0) } } /// @dev Returns if the cached domain separator has been invalidated. function _cachedDomainSeparatorInvalidated() private view returns (bool result) { uint256 cachedChainId = _cachedChainId; address cachedThis = _cachedThis; /// @solidity memory-safe-assembly assembly { result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis))) } } }
// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.4; /// @title Delegator /// @notice Exposes a `signer` address that allows an authorized EOA to sign off on actions on behalf of a contract /// @dev Allows developers to create Coordinator subscriptions off-chain, on behalf of a contract, by signing a /// `DelegateSubscription` from `signer` and submitting to `EIP712Coordinator.createSubscriptionDelegatee()` abstract contract Delegator { /*////////////////////////////////////////////////////////////// MUTABLE //////////////////////////////////////////////////////////////*/ /// @notice Authorized address with signing privileges /// @dev Recommended to use an EOA so that it can sign EIP-712 messages /// @dev Visibility is `public` to automatically generate and expose a getter address public signer; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /// @notice Initialize new Delegator /// @param signer_ authorized address constructor(address signer_) { signer = signer_; } /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Update delegated signer /// @dev No event is emitted given contract is meant to be inherited /// @param newSigner new delegated signer address function _updateSigner(address newSigner) internal { signer = newSigner; } }
// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.4; /// @title Manager /// @notice Manages node lifecycle (registration, activation, deactivation) /// @dev Allows anyone to register to become an active node /// @dev Allows registered nodes to become active after a `cooldown` seconds waiting period /// @dev Allows any node to deactivate itself and return to an inactive state /// @dev Exposes an `onlyActiveNode()` modifier used to restrict functions to being called by only active nodes /// @dev Restricts addresses to 1 of 3 states: `Inactive`, `Registered`, `Active` abstract contract Manager { /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ /// @notice Packed information about a node (status, cooldown start) /// @dev Cheaper to use a struct to store `status` + `cooldownStart` rather than SSTORE 2 independent mappings /// @dev Technically, could bitshift pack uint40 of data into single uint256 but readability penalty not worth it /// @dev Tightly-packed (well under 32-byte slot): [uint8, uint32] = 40 bits = 5 bytes struct NodeInfo { /// @notice Node status NodeStatus status; /// @notice Cooldown start timestamp in seconds /// @dev Default initializes to `0`; no cooldown active to start /// @dev Equal to `0` if `status != NodeStatus.Registered`, else equal to cooldown start time /// @dev Is modified by `registerNode()` to initiate `cooldown` holding period /// @dev uint32 allows for a timestamp up to year ~2106, likely far beyond lifecycle of this contract uint32 cooldownStart; } /*////////////////////////////////////////////////////////////// ENUMS //////////////////////////////////////////////////////////////*/ /// @notice Possible node statuses /// @dev Enums in Solidity are unsigned integers capped at 256 members, so Inactive is the 0-initialized default /// @dev Inactive (0): Default status is inactive; no status /// @dev Registered (1): Node has registered to become active, initiating a period of `cooldown` /// @dev Active (2): Node is active, able to fulfill subscriptions, and is part of `modifier(onlyActiveNode)` enum NodeStatus { Inactive, Registered, Active } /*////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ /// @notice Cooldown period, in seconds, before a node with `NodeStatus.Registered` can call `activateNode()` /// @dev type(uint32) is sufficient but we are not packing variables so control plane costs are higher because we /// need to cast the 32-bit type into the 256-bit type anyways. Thus, we use type(uint256). uint256 public constant cooldown = 1 hours; /*////////////////////////////////////////////////////////////// MUTABLE //////////////////////////////////////////////////////////////*/ /// @dev Node address => node information mapping(address => NodeInfo) public nodeInfo; /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /// @notice Emitted when a node moves from `NodeStatus.Inactive` to `NodeStatus.Registered` /// @dev It's actually slightly more expensive (~6 gas) to emit the uint32 given the explicit conversion needed /// but this is necessary to have better readability and uniformity across the type (not casting in event) /// @param node newly-registered node address /// @param registerer optional proxy address registering on behalf of node (is equal to node when self-registering) /// @param cooldownStart start timestamp of registration cooldown event NodeRegistered(address indexed node, address indexed registerer, uint32 cooldownStart); /// @notice Emitted when a node moves from `NodeStatus.Registered` to `NodeStatus.Active` /// @param node newly-activated node address event NodeActivated(address indexed node); /// @notice Emitted when a node moves from any status to `NodeStatus.Inactive` /// @param node newly-deactivated node address event NodeDeactivated(address indexed node); /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown if attempting to call function that requires a node to have status `NodeStatus.Active` /// @dev Only used by `modifier(onlyActiveNode)` /// @dev 4-byte signature: `0x8741cbb8` error NodeNotActive(); /// @notice Thrown by `registerNode()` if attempting to register node with status that is not `NodeStatus.Inactive` /// @dev 4-byte signature: `0x5acfd518` /// @param node address of node attempting to register /// @param status current status of node failing registration error NodeNotRegisterable(address node, NodeStatus status); /// @notice Thrown by `activateNode()` if `cooldown` has not elapsed since node was registered /// @dev Like `NodeRegistered`, slightly more expensive to use uint32 over uint256 (~6 gas) but better readability /// @dev 4-byte signature: `0xc84b5bdd` /// @param cooldownStart start timestamp of node cooldown error CooldownActive(uint32 cooldownStart); /// @notice Thrown by `activateNode()` if attempting to active node with status that is not `NodeStatus.Registered` /// @dev 4-byte signature: `0x33daa7f9` /// @param status current status of node failing activation error NodeNotActivateable(NodeStatus status); /*////////////////////////////////////////////////////////////// MODIFIERS //////////////////////////////////////////////////////////////*/ /// @notice Allow only callers that are active nodes modifier onlyActiveNode() { if (nodeInfo[msg.sender].status != NodeStatus.Active) { revert NodeNotActive(); } _; } /*////////////////////////////////////////////////////////////// FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Allows registering a node for activation /// @dev First-step of two-step process (followed by `activateNode()`) /// @dev Can call on behalf of other nodes as a proxy registerer /// @dev Node must have `NodeStatus.Inactive` to begin registration /// @param node node address to register function registerNode(address node) external { // SLOAD node info NodeInfo storage info = nodeInfo[node]; // Ensure node is registerable // Current status must be `NodeStatus.Inactive` if (info.status != NodeStatus.Inactive) { revert NodeNotRegisterable(node, info.status); } // Update node status to Registered info.status = NodeStatus.Registered; // Update cooldown start timestamp to now info.cooldownStart = uint32(block.timestamp); // Emit new registration event emit NodeRegistered(node, msg.sender, uint32(block.timestamp)); } /// @notice Allows activating a registered node after `cooldown` has elapsed /// @dev Second-step of two-step process (preceeded by `registerNode()`) /// @dev Must be called by node accepting a pending registration (`msg.sender == node`) /// @dev Must be called at least `cooldown` seconds after `registerNode()` function activateNode() external { // SLOAD node info NodeInfo storage info = nodeInfo[msg.sender]; // Ensure node is already registered // Technically this check is not needed since the next check would fail anyways, but it provides a useful error if (info.status != NodeStatus.Registered) { revert NodeNotActivateable(info.status); } // Ensure node has elapsed required cooldown // Adding a uint32 to a uint32-bounded uint256 and upcasting to a uint256, so can't overflow uint256 cooldownEnd; unchecked { cooldownEnd = info.cooldownStart + cooldown; } if (block.timestamp < cooldownEnd) { revert CooldownActive(info.cooldownStart); } // Toggle node status to Active info.status = NodeStatus.Active; // Reset cooldown start timestamp info.cooldownStart = 0; // Emit activation event emit NodeActivated(msg.sender); } /// @notice Allows deactivating a node /// @dev Can be called to set the status of any node back to `NodeStatus.Inactive` with no cooldown /// @dev Must be called by the node deactivating itself (`msg.sender == node`) function deactivateNode() external { delete nodeInfo[msg.sender]; // Emit deactivation event emit NodeDeactivated(msg.sender); } }
// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.4; import {Coordinator} from "../Coordinator.sol"; /// @title BaseConsumer /// @notice Handles receiving container compute responses from Infernet coordinator /// @dev Contains a single public entrypoint `rawReceiveCompute` callable by only the Infernet coordinator. Once /// call origin is verified, parameters are proxied to internal function `_receiveCompute` abstract contract BaseConsumer { /*////////////////////////////////////////////////////////////// IMMUTABLE //////////////////////////////////////////////////////////////*/ /// @notice Infernet Coordinator /// @dev Internal visibility since COORDINATOR is consumed by inheriting contracts Coordinator internal immutable COORDINATOR; /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ /// @notice Thrown if attempting to call `rawReceiveCompute` from a `msg.sender != address(COORDINATOR)` /// @dev 4-byte signature: `0x9ec853e6` error NotCoordinator(); /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ /// @notice Initialize new BaseConsumer /// @param coordinator coordinator address constructor(address coordinator) { // Setup Coordinator COORDINATOR = Coordinator(coordinator); } /*////////////////////////////////////////////////////////////// VIRTUAL FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Callback entrypoint to receive container compute responses from validated Coordinator source /// @dev Called by `rawReceiveCompute` once validated that `msg.sender == address(COORDINATOR)` /// @dev Same function parameters as `rawReceiveCompute` /// @param subscriptionId id of subscription being responded to /// @param interval subscription interval /// @param redundancy after this call succeeds, how many nodes will have delivered a response for this interval /// @param node address of responding Infernet node /// @param input optional off-chain container input recorded by Infernet node (empty, hashed input, processed input, or both) /// @param output optional off-chain container output (empty, hashed output, processed output, both, or fallback: all encodeable data) /// @param proof optional off-chain container execution proof (or arbitrary metadata) function _receiveCompute( uint32 subscriptionId, uint32 interval, uint16 redundancy, address node, bytes calldata input, bytes calldata output, bytes calldata proof ) internal virtual {} /*////////////////////////////////////////////////////////////// FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @notice Callback entrypoint called by Infernet Coordinator to return container compute responses /// @dev Callable only by `address(COORDINATOR)`, else throws `NotCoordinator()` error /// @param subscriptionId id of subscription being responded to /// @param interval subscription interval /// @param redundancy after this call succeeds, how many nodes will have delivered a response for this interval /// @param node address of responding Infernet node /// @param input optional off-chain container input recorded by Infernet node (empty, hashed input, processed input, or both) /// @param output optional off-chain container output (empty, hashed output, processed output, both, or fallback: all encodeable data) /// @param proof optional off-chain container execution proof (or arbitrary metadata) function rawReceiveCompute( uint32 subscriptionId, uint32 interval, uint16 redundancy, address node, bytes calldata input, bytes calldata output, bytes calldata proof ) external { // Ensure caller is coordinator if (msg.sender != address(COORDINATOR)) { revert NotCoordinator(); } // Call internal receive function, since caller is validated _receiveCompute(subscriptionId, interval, redundancy, node, input, output, proof); } }
{ "remappings": [ "solady/=lib/solady/src/", "forge-std/=lib/forge-std/src/", "ds-test/=lib/forge-std/lib/ds-test/src/" ], "optimizer": { "enabled": true, "runs": 1000000 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "evmVersion": "paris", "viaIR": true, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"uint32","name":"cooldownStart","type":"uint32"}],"name":"CooldownActive","type":"error"},{"inputs":[],"name":"GasLimitExceeded","type":"error"},{"inputs":[],"name":"GasPriceExceeded","type":"error"},{"inputs":[],"name":"IntervalCompleted","type":"error"},{"inputs":[],"name":"IntervalMismatch","type":"error"},{"inputs":[{"internalType":"enum Manager.NodeStatus","name":"status","type":"uint8"}],"name":"NodeNotActivateable","type":"error"},{"inputs":[],"name":"NodeNotActive","type":"error"},{"inputs":[{"internalType":"address","name":"node","type":"address"},{"internalType":"enum Manager.NodeStatus","name":"status","type":"uint8"}],"name":"NodeNotRegisterable","type":"error"},{"inputs":[],"name":"NodeRespondedAlready","type":"error"},{"inputs":[],"name":"NotSubscriptionOwner","type":"error"},{"inputs":[],"name":"SignatureExpired","type":"error"},{"inputs":[],"name":"SignerMismatch","type":"error"},{"inputs":[],"name":"SubscriptionCompleted","type":"error"},{"inputs":[],"name":"SubscriptionNotActive","type":"error"},{"inputs":[],"name":"SubscriptionNotFound","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"node","type":"address"}],"name":"NodeActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"node","type":"address"}],"name":"NodeDeactivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"node","type":"address"},{"indexed":true,"internalType":"address","name":"registerer","type":"address"},{"indexed":false,"internalType":"uint32","name":"cooldownStart","type":"uint32"}],"name":"NodeRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"id","type":"uint32"}],"name":"SubscriptionCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"id","type":"uint32"}],"name":"SubscriptionCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"id","type":"uint32"},{"indexed":true,"internalType":"address","name":"node","type":"address"}],"name":"SubscriptionFulfilled","type":"event"},{"inputs":[],"name":"DELEGATEE_OVERHEAD_CACHED_WEI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DELEGATEE_OVERHEAD_CREATE_WEI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DELIVERY_OVERHEAD_WEI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"activateNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"subscriptionId","type":"uint32"}],"name":"cancelSubscription","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cooldown","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"containerId","type":"string"},{"internalType":"bytes","name":"inputs","type":"bytes"},{"internalType":"uint48","name":"maxGasPrice","type":"uint48"},{"internalType":"uint32","name":"maxGasLimit","type":"uint32"},{"internalType":"uint32","name":"frequency","type":"uint32"},{"internalType":"uint32","name":"period","type":"uint32"},{"internalType":"uint16","name":"redundancy","type":"uint16"}],"name":"createSubscription","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint32","name":"expiry","type":"uint32"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint32","name":"activeAt","type":"uint32"},{"internalType":"uint32","name":"period","type":"uint32"},{"internalType":"uint32","name":"frequency","type":"uint32"},{"internalType":"uint16","name":"redundancy","type":"uint16"},{"internalType":"uint48","name":"maxGasPrice","type":"uint48"},{"internalType":"uint32","name":"maxGasLimit","type":"uint32"},{"internalType":"string","name":"containerId","type":"string"},{"internalType":"bytes","name":"inputs","type":"bytes"}],"internalType":"struct Coordinator.Subscription","name":"sub","type":"tuple"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"createSubscriptionDelegatee","outputs":[{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deactivateNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"delegateCreatedIds","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"subscriptionId","type":"uint32"},{"internalType":"uint32","name":"deliveryInterval","type":"uint32"},{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"bytes","name":"output","type":"bytes"},{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"deliverCompute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint32","name":"expiry","type":"uint32"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint32","name":"activeAt","type":"uint32"},{"internalType":"uint32","name":"period","type":"uint32"},{"internalType":"uint32","name":"frequency","type":"uint32"},{"internalType":"uint16","name":"redundancy","type":"uint16"},{"internalType":"uint48","name":"maxGasPrice","type":"uint48"},{"internalType":"uint32","name":"maxGasLimit","type":"uint32"},{"internalType":"string","name":"containerId","type":"string"},{"internalType":"bytes","name":"inputs","type":"bytes"}],"internalType":"struct Coordinator.Subscription","name":"sub","type":"tuple"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint32","name":"deliveryInterval","type":"uint32"},{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"bytes","name":"output","type":"bytes"},{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"deliverComputeDelegatee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"activeAt","type":"uint32"},{"internalType":"uint32","name":"period","type":"uint32"}],"name":"getSubscriptionInterval","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"id","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxSubscriberNonce","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nodeInfo","outputs":[{"internalType":"enum Manager.NodeStatus","name":"status","type":"uint8"},{"internalType":"uint32","name":"cooldownStart","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"nodeResponded","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"redundancyCount","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"node","type":"address"}],"name":"registerNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"subscriptions","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint32","name":"activeAt","type":"uint32"},{"internalType":"uint32","name":"period","type":"uint32"},{"internalType":"uint32","name":"frequency","type":"uint32"},{"internalType":"uint16","name":"redundancy","type":"uint16"},{"internalType":"uint48","name":"maxGasPrice","type":"uint48"},{"internalType":"uint32","name":"maxGasLimit","type":"uint32"},{"internalType":"string","name":"containerId","type":"string"},{"internalType":"bytes","name":"inputs","type":"bytes"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
6101203461012b57306080524660a0526040906001600160401b03908083018281118282101761011557835260138152602081017f496e6665726e6574436f6f7264696e61746f72000000000000000000000000008152835190848201938285109085111761011557602060019260a09587528381520192603160f81b845251902091208160c0528060e0528351917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f83526020830152838201524660608201523060808201522090610100918252600163ffffffff198154161760015551612baf918261013183396080518261233a015260a0518261235d015260c05182612adf015260e05182612b06015251816123180152f35b634e487b7160e01b600052604160045260246000fd5b600080fdfe608080604052600436101561001357600080fd5b60003560e01c908163105ddd1d14611ae65750806323c2e2bc14611a67578063298f7bdc14611a1857806331e451a91461189e5780633b2fb7a81461186457806341770e9a146117cf57806341cf2b7f146117945780634d9bf22f1461112957806360ed0f61146110d9578063642012c714610c8d578063672d7a0d14610b715780636e021332146105d057806373fb5c3a146104c4578063787a08a6146104895780637fb61b271461041b578063836a6cc9146103c357806384b0196e146102e157806390b04c15146102a657806396ef592e1461023f578063af640d0f146101fd578063bc85694f146101af578063e7cab346146101735763eccec5a81461011c57600080fd5b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5761016a610156611f22565b604051918291602083526020830190611d19565b0390f35b600080fd5b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576020604051620164408152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576004356000526003602052602061ffff60406000205416604051908152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57602063ffffffff60015416604051908152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5733600052600060205260006040812055337fd9957750e6343405c319eb99a4ec67fa11cfd66969318cbc71aa2d45fa53a349600080a2005b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57602060405161dd188152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5761037161031b611ce0565b610323611f22565b906040519283927f0f00000000000000000000000000000000000000000000000000000000000000845261036360209360e08587015260e0860190611d19565b908482036040860152611d19565b90466060840152306080840152600060a084015282820360c08401528060605192838152019160809160005b8281106103ac57505050500390f35b83518552869550938101939281019260010161039d565b3461016e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57602061040d6103ff611c11565b610407611c24565b90611fb9565b63ffffffff60405191168152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043573ffffffffffffffffffffffffffffffffffffffff811680910361016e576000526005602052602063ffffffff60406000205416604051908152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576020604051610e108152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5763ffffffff80610501611c11565b166000526004602052604060002061016a60036105c183549360018101549561054f6040519261053f846105388160028501611e6d565b0385611c9f565b6105386040518097819301611e6d565b604051968673ffffffffffffffffffffffffffffffffffffffff8998168852828160a01c166020890152828160c01c16604089015260e01c606088015261ffff8116608088015265ffffffffffff8160101c1660a088015260401c1660c08601526101208060e0870152850190611d19565b90838203610100850152611d19565b3461016e5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57610607611c11565b61060f611c24565b67ffffffffffffffff9060443582811161016e57610631903690600401611dec565b92909160643582811161016e5761064c903690600401611dec565b91909260843590811161016e57610667903690600401611dec565b91909533600052600060205260ff604060002054166003811015610b4257600203610b1857600096604080518a815260046020820152209384549473ffffffffffffffffffffffffffffffffffffffff861615610aee5763ffffffff8660a01c16804210610ac45763ffffffff6106e58192828a60c01c1690611fb9565b1695168503610a9a578560e01c8511610a7057600101549665ffffffffffff8860101c163a11610a4657604051602081019063ffffffff8d1682528660408201526040815261073381611c4b565b5190209687600052600360205261ffff604060002054169761ffff8a168914610a1c576000526003602052604060002061ffff60018a01167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00008254161790558b60405163ffffffff6020820192168252876040820152336060820152606081526107bc81611c67565b51902080600052600260205260ff604060002054166109f2576000526002602052604060002060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790555a9961ffff60018a01116109c35773ffffffffffffffffffffffffffffffffffffffff88163b1561016e578c95604051998a9889987ff8eb7fbd000000000000000000000000000000000000000000000000000000008a5263ffffffff1660048a0152602489015260010161ffff1660448801523360648801526084870160e0905260e487019061089b92611fde565b908582037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160a48701526108cf92611fde565b908382037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160c485015261090392611fde565b039173ffffffffffffffffffffffffffffffffffffffff1691815a6000948591f180156109b75761099f575b5063ffffffff61dd189160401c16915a90030111610975577fc68fb0ae5cea2793405d29014d881bcda18f67122e0bcd7d0a577e118b64e4c863ffffffff3393169180a3005b60046040517fbe9179a6000000000000000000000000000000000000000000000000000000008152fd5b6109aa919350611c37565b60009163ffffffff61092f565b6040513d6000823e3d90fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60046040517f88a21e4f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f2f4ca85b000000000000000000000000000000000000000000000000000000008152fd5b60046040517f682bad5a000000000000000000000000000000000000000000000000000000008152fd5b60046040517fae6704a7000000000000000000000000000000000000000000000000000000008152fd5b60046040517f4db310c3000000000000000000000000000000000000000000000000000000008152fd5b60046040517fefb74efe000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1a00354f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8741cbb8000000000000000000000000000000000000000000000000000000008152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043573ffffffffffffffffffffffffffffffffffffffff811680910361016e578060005260006020526040600020805460ff81166003811015610b425780610c4e57506001907fffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000064ffffffff004260081b169116171790556040519063ffffffff421682527fb73af334a40cdaaad72e06d597bdeed270fc94d45415863afec219108096d2e860203393a3005b83610c8b604492604051927f5acfd51800000000000000000000000000000000000000000000000000000000845260048401526024830190611c04565bfd5b3461016e576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57610cc5611c11565b610ccd611c24565b67ffffffffffffffff91826044351161016e576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6044353603011261016e57610d17611d77565b60c4359363ffffffff8516850361016e5760e43581811161016e57610d40903690600401611dec565b9290936101043583811161016e57610d5c903690600401611dec565b9290936101243590811161016e57610d78903690600401611dec565b92909733600052600060205260ff604060002054166003811015610b4257600203610b1857610db59260a4359260843592604435600401916120c2565b909790156110cf57610258945b604080518a815260046020820152209788549360009973ffffffffffffffffffffffffffffffffffffffff861615610aee5763ffffffff8660a01c16804210610ac45763ffffffff610e1b8192828a60c01c1690611fb9565b1695168503610a9a578560e01c8511610a7057600101549665ffffffffffff8860101c163a11610a465760405163ffffffff8d16602082015285604082015260408152610e6781611c4b565b602081519101209687600052600360205261ffff604060002054169761ffff8a168914610a1c576000526003602052604060002061ffff60018a01167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00008254161790558c60405163ffffffff602082019216825287604082015233606082015260608152610ef481611c67565b51902080600052600260205260ff604060002054166109f2576000526002602052604060002060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790555a9a61ffff60018a01116109c35773ffffffffffffffffffffffffffffffffffffffff88163b1561016e578d95604051998a9889987ff8eb7fbd000000000000000000000000000000000000000000000000000000008a5263ffffffff1660048a0152602489015260010161ffff1660448801523360648801526084870160e0905260e4870190610fd392611fde565b908582037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160a487015261100792611fde565b908382037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160c485015261103b92611fde565b039173ffffffffffffffffffffffffffffffffffffffff1691815a6000948591f180156109b7576110af575b509063ffffffff61dd189260401c16925a9003010111610975577fc68fb0ae5cea2793405d29014d881bcda18f67122e0bcd7d0a577e118b64e4c863ffffffff3393169180a3005b61dd18929194506110bf90611c37565b63ffffffff600094919250611067565b6201644094610dc2565b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576004356000526006602052602063ffffffff60406000205416604051908152f35b3461016e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043567ffffffffffffffff811161016e573660238201121561016e57611189903690602481600401359101611d87565b60243567ffffffffffffffff811161016e576111a9903690600401611dec565b90916044359265ffffffffffff8416840361016e576064359263ffffffff8416840361016e576084359263ffffffff8416840361016e5760a4359463ffffffff8616860361016e5760c4359061ffff8216820361016e576001549663ffffffff6001818a1601167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000089161760015563ffffffff80821681421601116109c357604051988961012081011067ffffffffffffffff6101208c0111176116b05761ffff63ffffffff94856112c19a8d60408365ffffffffffff98610120840183523384528180821681421601166020850152169101521660608d01521660808b01521660a08901521660c087015260e08601523691611d87565b61010083015263ffffffff81166000526004602052604060002073ffffffffffffffffffffffffffffffffffffffff8351167fffffffffffffffffffffffff000000000000000000000000000000000000000082541617815561137263ffffffff60208501511682907fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff77ffffffff000000000000000000000000000000000000000083549260a01b169116179055565b604083015181547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7bffffffff00000000000000000000000000000000000000000000000016178155606083015181547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09190911b7fffffffff00000000000000000000000000000000000000000000000000000000161781556114d56001820161ffff6080860151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000082541617815561149365ffffffffffff60a08701511682907fffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffff67ffffffffffff000083549260101b169116179055565b60c085015181547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff1660409190911b6bffffffff000000000000000016179055565b6002810160e084015180519067ffffffffffffffff82116116b057611504826114fe8554611e1a565b85611f72565b602090601f83116001146116ea5791806003949261010096946000926116df575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b92861b1c19161790555b019201519081519267ffffffffffffffff84116116b0578361158460209561157e8454611e1a565b84611f72565b8493601f8211600114611611579381929394600092611606575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790555b63ffffffff604051918181167f04344ed7a67fec80c444d56ee1cee242f3f75b91fecc8dbce8890069c82eb48e600080a2168152f35b01519050858061159e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169483600052866000209160005b878110611699575083600195969710611662575b505050811b0190556115d0565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611655565b919288600181928685015181550194019201611641565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b015190508880611525565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316918460005260206000209260005b81811061177c575092600192859261010098966003989610611746575b505050811b019055611556565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f884891b161c19169055888080611739565b9293602060018192878601518155019501930161171c565b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760206040516102588152f35b3461016e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60c08136011261016e57611807611c11565b61180f611c24565b6044359267ffffffffffffffff841161016e5761012090843603011261016e576040926118509261183e611d77565b9060a4359360843593600401916120c2565b63ffffffff83519216825215156020820152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5761016a610156611ce0565b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5763ffffffff6118da611c11565b1680600052600460205273ffffffffffffffffffffffffffffffffffffffff6040600020541633036119ee578060005260046020526003604060002060008155600060018201556002810161192f8154611e1a565b90816119b0575b5050016119438154611e1a565b9081611972575b827ff4126e31c182db4c4109605c6d50470fc7e8ca90d62d44fd25cbe049fb9cac3e600080a2005b81601f6000931160011461198a5750555b818061194a565b9080839182526119a9601f60208420940160051c840160018501611f5b565b5555611983565b81601f600093116001146119c85750555b8380611936565b9080839182526119e7601f60208420940160051c840160018501611f5b565b55556119c1565b60046040517fa7fba711000000000000000000000000000000000000000000000000000000008152fd5b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576004356000526002602052602060ff604060002054166040519015158152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043573ffffffffffffffffffffffffffffffffffffffff811680910361016e5760005260006020526040806000205463ffffffff825191611adb8360ff8316611c04565b60081c166020820152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5733600052600060205260406000209081549060ff82166003811015610b425760018103611bd457505063ffffffff8160081c16610e1081014210611ba357507fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000166002179055337f7dc8b937d2916b130743c447af3d771fa55e66b7393105150e2e635ac3e87260600080a2005b602490604051907fc84b5bdd0000000000000000000000000000000000000000000000000000000082526004820152fd5b90610c8b6024927f33daa7f900000000000000000000000000000000000000000000000000000000835260048301905b906003821015610b425752565b6004359063ffffffff8216820361016e57565b6024359063ffffffff8216820361016e57565b67ffffffffffffffff81116116b057604052565b6060810190811067ffffffffffffffff8211176116b057604052565b6080810190811067ffffffffffffffff8211176116b057604052565b6040810190811067ffffffffffffffff8211176116b057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176116b057604052565b60405190611ced82611c83565b601382527f496e6665726e6574436f6f7264696e61746f72000000000000000000000000006020830152565b919082519283825260005b848110611d635750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b602081830181015184830182015201611d24565b6064359060ff8216820361016e57565b92919267ffffffffffffffff82116116b05760405191611dcf60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184611c9f565b82948184528183011161016e578281602093846000960137010152565b9181601f8401121561016e5782359167ffffffffffffffff831161016e576020838186019501011161016e57565b90600182811c92168015611e63575b6020831014611e3457565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611e29565b9060009291805491611e7e83611e1a565b918282526001938481169081600014611ee05750600114611ea0575b50505050565b90919394506000526020928360002092846000945b838610611ecc575050505001019038808080611e9a565b805485870183015294019385908201611eb5565b91505060209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009193501683830152151560051b01019038808080611e9a565b60405190611f2f82611c83565b600182527f31000000000000000000000000000000000000000000000000000000000000006020830152565b818110611f66575050565b60008155600101611f5b565b9190601f8111611f8157505050565b611fad926000526020600020906020601f840160051c83019310611faf575b601f0160051c0190611f5b565b565b9091508190611fa0565b63ffffffff8092168015611fd65782600192814216031604011690565b505050600190565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361016e5790565b3563ffffffff8116810361016e5790565b3561ffff8116810361016e5790565b3565ffffffffffff8116810361016e5790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561016e570180359067ffffffffffffffff821161016e5760200191813603831361016e57565b929193959490956120d28561201d565b60405173ffffffffffffffffffffffffffffffffffffffff602082019216825263ffffffff861660408201526040815261210b81611c4b565b5190209687600052600660205263ffffffff6040600020541680612b6b575063ffffffff811663ffffffff42161015612b41576121478661201d565b906121546020880161203e565b6121606040890161203e565b61216c60608a0161203e565b61217860808b0161204f565b61218460a08c0161205e565b9061219160c08d0161203e565b9261219f60e08e018e612071565b36906121aa92611d87565b80519060200120948d61010081016121c191612071565b36906121cc92611d87565b80519060200120966040519960208b017f2b24aa047bbff05c807b5ba16020ac01534fdb34947d5608264ac56133753190905273ffffffffffffffffffffffffffffffffffffffff1660408b015263ffffffff1660608a015263ffffffff16608089015263ffffffff1660a088015261ffff1660c087015265ffffffffffff1660e086015263ffffffff166101008501526101208401526101409081840152825281610160810110610160830167ffffffffffffffff10176116b057610160820160405263ffffffff82516020840120917f1950b0dfc42b437b752641a63d09bd2a7d858914bdd31db6ef0dc3e5b2bf544e6101808501528188166101a0850152166101c08301526101e08201526080610160820152610160810161020082011067ffffffffffffffff610200830111176116b0576102008101604052610160810151610180820120907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000030147f000000000000000000000000000000000000000000000000000000000000000046141615612ab0575b50671901000000000000600052601a52603a5260ff6042601820936000603a5260405194600052166020526040526060526020600160806000825afa51903d15612aa25760006060528060405260208160048173ffffffffffffffffffffffffffffffffffffffff6123f68961201d565b167f238ac9330000000000000000000000000000000000000000000000000000000082525afa9081156109b757600091612a3f575b5073ffffffffffffffffffffffffffffffffffffffff809116911603612a15576001549163ffffffff600181851601167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000084161760015563ffffffff83166000526004602052604060002073ffffffffffffffffffffffffffffffffffffffff6124b48361201d565b167fffffffffffffffffffffffff00000000000000000000000000000000000000008254161781556125346124eb6020840161203e565b82547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b77ffffffff000000000000000000000000000000000000000016178255565b6125906125436040840161203e565b82547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7bffffffff00000000000000000000000000000000000000000000000016178255565b6125ec61259f6060840161203e565b82547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09190911b7fffffffff0000000000000000000000000000000000000000000000000000000016178255565b6126ba6001820161ffff6126026080860161204f565b167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000082541617815561267261263960a0860161205e565b82547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffff1660109190911b67ffffffffffff000016178255565b61267e60c0850161203e565b7fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff6bffffffff000000000000000083549260401b169116179055565b6126c760e0830183612071565b67ffffffffffffffff81116116b05760028301916126e9826114fe8554611e1a565b600090601f831160011461296f5760039493929160009183612964575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b92861b1c19161790555b0194612747610100830183612071565b67ffffffffffffffff81989298116116b0576127678161157e8454611e1a565b6000601f82116001146128ba57819063ffffffff98996000926128af575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790555b600052600660205260406000208484167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008254161790558383167f04344ed7a67fec80c444d56ee1cee242f3f75b91fecc8dbce8890069c82eb48e600080a273ffffffffffffffffffffffffffffffffffffffff6128348261201d565b166000526005602052836040600020541684831611612857575b50501690600090565b61287573ffffffffffffffffffffffffffffffffffffffff9161201d565b1660005282604060002091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000825416179055388061284e565b013590503880612785565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216908360005260206000209160005b81811061294c57509983929160019463ffffffff9b9c10612914575b505050811b0190556127b7565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88560031b161c19910135169055388080612907565b9192602060018192868f0135815501940192016128eb565b013590503880612706565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316918460005260206000209260005b8181106129fd5750916001939185600398979694106129c7575b505050811b019055612737565b01357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83881b60f8161c191690553880806129ba565b919360206001819287870135815501950192016129a0565b60046040517f10c74b03000000000000000000000000000000000000000000000000000000008152fd5b90506020813d602011612a9a575b81612a5a60209383611c9f565b8101031261016e575173ffffffffffffffffffffffffffffffffffffffff8116810361016e5773ffffffffffffffffffffffffffffffffffffffff61242b565b3d9150612a4d565b638baa579f6000526004601cfd5b60a09150807f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610200809301527f00000000000000000000000000000000000000000000000000000000000000006102208201527f00000000000000000000000000000000000000000000000000000000000000006102408201524661026082015230610280820152012038612385565b60046040517f0819bdcd000000000000000000000000000000000000000000000000000000008152fd5b97506001969550505050505056fea264697066735822122095589eb4b5ab0766de415e6daf4ff18a4ce5190aac1c3052b77c63cb77e91b7464736f6c63430008130033
Deployed Bytecode
0x608080604052600436101561001357600080fd5b60003560e01c908163105ddd1d14611ae65750806323c2e2bc14611a67578063298f7bdc14611a1857806331e451a91461189e5780633b2fb7a81461186457806341770e9a146117cf57806341cf2b7f146117945780634d9bf22f1461112957806360ed0f61146110d9578063642012c714610c8d578063672d7a0d14610b715780636e021332146105d057806373fb5c3a146104c4578063787a08a6146104895780637fb61b271461041b578063836a6cc9146103c357806384b0196e146102e157806390b04c15146102a657806396ef592e1461023f578063af640d0f146101fd578063bc85694f146101af578063e7cab346146101735763eccec5a81461011c57600080fd5b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5761016a610156611f22565b604051918291602083526020830190611d19565b0390f35b600080fd5b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576020604051620164408152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576004356000526003602052602061ffff60406000205416604051908152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57602063ffffffff60015416604051908152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5733600052600060205260006040812055337fd9957750e6343405c319eb99a4ec67fa11cfd66969318cbc71aa2d45fa53a349600080a2005b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57602060405161dd188152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5761037161031b611ce0565b610323611f22565b906040519283927f0f00000000000000000000000000000000000000000000000000000000000000845261036360209360e08587015260e0860190611d19565b908482036040860152611d19565b90466060840152306080840152600060a084015282820360c08401528060605192838152019160809160005b8281106103ac57505050500390f35b83518552869550938101939281019260010161039d565b3461016e5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57602061040d6103ff611c11565b610407611c24565b90611fb9565b63ffffffff60405191168152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043573ffffffffffffffffffffffffffffffffffffffff811680910361016e576000526005602052602063ffffffff60406000205416604051908152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576020604051610e108152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5763ffffffff80610501611c11565b166000526004602052604060002061016a60036105c183549360018101549561054f6040519261053f846105388160028501611e6d565b0385611c9f565b6105386040518097819301611e6d565b604051968673ffffffffffffffffffffffffffffffffffffffff8998168852828160a01c166020890152828160c01c16604089015260e01c606088015261ffff8116608088015265ffffffffffff8160101c1660a088015260401c1660c08601526101208060e0870152850190611d19565b90838203610100850152611d19565b3461016e5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57610607611c11565b61060f611c24565b67ffffffffffffffff9060443582811161016e57610631903690600401611dec565b92909160643582811161016e5761064c903690600401611dec565b91909260843590811161016e57610667903690600401611dec565b91909533600052600060205260ff604060002054166003811015610b4257600203610b1857600096604080518a815260046020820152209384549473ffffffffffffffffffffffffffffffffffffffff861615610aee5763ffffffff8660a01c16804210610ac45763ffffffff6106e58192828a60c01c1690611fb9565b1695168503610a9a578560e01c8511610a7057600101549665ffffffffffff8860101c163a11610a4657604051602081019063ffffffff8d1682528660408201526040815261073381611c4b565b5190209687600052600360205261ffff604060002054169761ffff8a168914610a1c576000526003602052604060002061ffff60018a01167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00008254161790558b60405163ffffffff6020820192168252876040820152336060820152606081526107bc81611c67565b51902080600052600260205260ff604060002054166109f2576000526002602052604060002060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790555a9961ffff60018a01116109c35773ffffffffffffffffffffffffffffffffffffffff88163b1561016e578c95604051998a9889987ff8eb7fbd000000000000000000000000000000000000000000000000000000008a5263ffffffff1660048a0152602489015260010161ffff1660448801523360648801526084870160e0905260e487019061089b92611fde565b908582037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160a48701526108cf92611fde565b908382037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160c485015261090392611fde565b039173ffffffffffffffffffffffffffffffffffffffff1691815a6000948591f180156109b75761099f575b5063ffffffff61dd189160401c16915a90030111610975577fc68fb0ae5cea2793405d29014d881bcda18f67122e0bcd7d0a577e118b64e4c863ffffffff3393169180a3005b60046040517fbe9179a6000000000000000000000000000000000000000000000000000000008152fd5b6109aa919350611c37565b60009163ffffffff61092f565b6040513d6000823e3d90fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60046040517f88a21e4f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f2f4ca85b000000000000000000000000000000000000000000000000000000008152fd5b60046040517f682bad5a000000000000000000000000000000000000000000000000000000008152fd5b60046040517fae6704a7000000000000000000000000000000000000000000000000000000008152fd5b60046040517f4db310c3000000000000000000000000000000000000000000000000000000008152fd5b60046040517fefb74efe000000000000000000000000000000000000000000000000000000008152fd5b60046040517f1a00354f000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8741cbb8000000000000000000000000000000000000000000000000000000008152fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043573ffffffffffffffffffffffffffffffffffffffff811680910361016e578060005260006020526040600020805460ff81166003811015610b425780610c4e57506001907fffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000064ffffffff004260081b169116171790556040519063ffffffff421682527fb73af334a40cdaaad72e06d597bdeed270fc94d45415863afec219108096d2e860203393a3005b83610c8b604492604051927f5acfd51800000000000000000000000000000000000000000000000000000000845260048401526024830190611c04565bfd5b3461016e576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e57610cc5611c11565b610ccd611c24565b67ffffffffffffffff91826044351161016e576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc6044353603011261016e57610d17611d77565b60c4359363ffffffff8516850361016e5760e43581811161016e57610d40903690600401611dec565b9290936101043583811161016e57610d5c903690600401611dec565b9290936101243590811161016e57610d78903690600401611dec565b92909733600052600060205260ff604060002054166003811015610b4257600203610b1857610db59260a4359260843592604435600401916120c2565b909790156110cf57610258945b604080518a815260046020820152209788549360009973ffffffffffffffffffffffffffffffffffffffff861615610aee5763ffffffff8660a01c16804210610ac45763ffffffff610e1b8192828a60c01c1690611fb9565b1695168503610a9a578560e01c8511610a7057600101549665ffffffffffff8860101c163a11610a465760405163ffffffff8d16602082015285604082015260408152610e6781611c4b565b602081519101209687600052600360205261ffff604060002054169761ffff8a168914610a1c576000526003602052604060002061ffff60018a01167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00008254161790558c60405163ffffffff602082019216825287604082015233606082015260608152610ef481611c67565b51902080600052600260205260ff604060002054166109f2576000526002602052604060002060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790555a9a61ffff60018a01116109c35773ffffffffffffffffffffffffffffffffffffffff88163b1561016e578d95604051998a9889987ff8eb7fbd000000000000000000000000000000000000000000000000000000008a5263ffffffff1660048a0152602489015260010161ffff1660448801523360648801526084870160e0905260e4870190610fd392611fde565b908582037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160a487015261100792611fde565b908382037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0160c485015261103b92611fde565b039173ffffffffffffffffffffffffffffffffffffffff1691815a6000948591f180156109b7576110af575b509063ffffffff61dd189260401c16925a9003010111610975577fc68fb0ae5cea2793405d29014d881bcda18f67122e0bcd7d0a577e118b64e4c863ffffffff3393169180a3005b61dd18929194506110bf90611c37565b63ffffffff600094919250611067565b6201644094610dc2565b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576004356000526006602052602063ffffffff60406000205416604051908152f35b3461016e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043567ffffffffffffffff811161016e573660238201121561016e57611189903690602481600401359101611d87565b60243567ffffffffffffffff811161016e576111a9903690600401611dec565b90916044359265ffffffffffff8416840361016e576064359263ffffffff8416840361016e576084359263ffffffff8416840361016e5760a4359463ffffffff8616860361016e5760c4359061ffff8216820361016e576001549663ffffffff6001818a1601167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000089161760015563ffffffff80821681421601116109c357604051988961012081011067ffffffffffffffff6101208c0111176116b05761ffff63ffffffff94856112c19a8d60408365ffffffffffff98610120840183523384528180821681421601166020850152169101521660608d01521660808b01521660a08901521660c087015260e08601523691611d87565b61010083015263ffffffff81166000526004602052604060002073ffffffffffffffffffffffffffffffffffffffff8351167fffffffffffffffffffffffff000000000000000000000000000000000000000082541617815561137263ffffffff60208501511682907fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff77ffffffff000000000000000000000000000000000000000083549260a01b169116179055565b604083015181547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7bffffffff00000000000000000000000000000000000000000000000016178155606083015181547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09190911b7fffffffff00000000000000000000000000000000000000000000000000000000161781556114d56001820161ffff6080860151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000082541617815561149365ffffffffffff60a08701511682907fffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffff67ffffffffffff000083549260101b169116179055565b60c085015181547fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff1660409190911b6bffffffff000000000000000016179055565b6002810160e084015180519067ffffffffffffffff82116116b057611504826114fe8554611e1a565b85611f72565b602090601f83116001146116ea5791806003949261010096946000926116df575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b92861b1c19161790555b019201519081519267ffffffffffffffff84116116b0578361158460209561157e8454611e1a565b84611f72565b8493601f8211600114611611579381929394600092611606575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790555b63ffffffff604051918181167f04344ed7a67fec80c444d56ee1cee242f3f75b91fecc8dbce8890069c82eb48e600080a2168152f35b01519050858061159e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169483600052866000209160005b878110611699575083600195969710611662575b505050811b0190556115d0565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611655565b919288600181928685015181550194019201611641565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b015190508880611525565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316918460005260206000209260005b81811061177c575092600192859261010098966003989610611746575b505050811b019055611556565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f884891b161c19169055888080611739565b9293602060018192878601518155019501930161171c565b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760206040516102588152f35b3461016e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60c08136011261016e57611807611c11565b61180f611c24565b6044359267ffffffffffffffff841161016e5761012090843603011261016e576040926118509261183e611d77565b9060a4359360843593600401916120c2565b63ffffffff83519216825215156020820152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5761016a610156611ce0565b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5763ffffffff6118da611c11565b1680600052600460205273ffffffffffffffffffffffffffffffffffffffff6040600020541633036119ee578060005260046020526003604060002060008155600060018201556002810161192f8154611e1a565b90816119b0575b5050016119438154611e1a565b9081611972575b827ff4126e31c182db4c4109605c6d50470fc7e8ca90d62d44fd25cbe049fb9cac3e600080a2005b81601f6000931160011461198a5750555b818061194a565b9080839182526119a9601f60208420940160051c840160018501611f5b565b5555611983565b81601f600093116001146119c85750555b8380611936565b9080839182526119e7601f60208420940160051c840160018501611f5b565b55556119c1565b60046040517fa7fba711000000000000000000000000000000000000000000000000000000008152fd5b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e576004356000526002602052602060ff604060002054166040519015158152f35b3461016e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5760043573ffffffffffffffffffffffffffffffffffffffff811680910361016e5760005260006020526040806000205463ffffffff825191611adb8360ff8316611c04565b60081c166020820152f35b3461016e5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016e5733600052600060205260406000209081549060ff82166003811015610b425760018103611bd457505063ffffffff8160081c16610e1081014210611ba357507fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000166002179055337f7dc8b937d2916b130743c447af3d771fa55e66b7393105150e2e635ac3e87260600080a2005b602490604051907fc84b5bdd0000000000000000000000000000000000000000000000000000000082526004820152fd5b90610c8b6024927f33daa7f900000000000000000000000000000000000000000000000000000000835260048301905b906003821015610b425752565b6004359063ffffffff8216820361016e57565b6024359063ffffffff8216820361016e57565b67ffffffffffffffff81116116b057604052565b6060810190811067ffffffffffffffff8211176116b057604052565b6080810190811067ffffffffffffffff8211176116b057604052565b6040810190811067ffffffffffffffff8211176116b057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176116b057604052565b60405190611ced82611c83565b601382527f496e6665726e6574436f6f7264696e61746f72000000000000000000000000006020830152565b919082519283825260005b848110611d635750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b602081830181015184830182015201611d24565b6064359060ff8216820361016e57565b92919267ffffffffffffffff82116116b05760405191611dcf60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184611c9f565b82948184528183011161016e578281602093846000960137010152565b9181601f8401121561016e5782359167ffffffffffffffff831161016e576020838186019501011161016e57565b90600182811c92168015611e63575b6020831014611e3457565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611e29565b9060009291805491611e7e83611e1a565b918282526001938481169081600014611ee05750600114611ea0575b50505050565b90919394506000526020928360002092846000945b838610611ecc575050505001019038808080611e9a565b805485870183015294019385908201611eb5565b91505060209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009193501683830152151560051b01019038808080611e9a565b60405190611f2f82611c83565b600182527f31000000000000000000000000000000000000000000000000000000000000006020830152565b818110611f66575050565b60008155600101611f5b565b9190601f8111611f8157505050565b611fad926000526020600020906020601f840160051c83019310611faf575b601f0160051c0190611f5b565b565b9091508190611fa0565b63ffffffff8092168015611fd65782600192814216031604011690565b505050600190565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361016e5790565b3563ffffffff8116810361016e5790565b3561ffff8116810361016e5790565b3565ffffffffffff8116810361016e5790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561016e570180359067ffffffffffffffff821161016e5760200191813603831361016e57565b929193959490956120d28561201d565b60405173ffffffffffffffffffffffffffffffffffffffff602082019216825263ffffffff861660408201526040815261210b81611c4b565b5190209687600052600660205263ffffffff6040600020541680612b6b575063ffffffff811663ffffffff42161015612b41576121478661201d565b906121546020880161203e565b6121606040890161203e565b61216c60608a0161203e565b61217860808b0161204f565b61218460a08c0161205e565b9061219160c08d0161203e565b9261219f60e08e018e612071565b36906121aa92611d87565b80519060200120948d61010081016121c191612071565b36906121cc92611d87565b80519060200120966040519960208b017f2b24aa047bbff05c807b5ba16020ac01534fdb34947d5608264ac56133753190905273ffffffffffffffffffffffffffffffffffffffff1660408b015263ffffffff1660608a015263ffffffff16608089015263ffffffff1660a088015261ffff1660c087015265ffffffffffff1660e086015263ffffffff166101008501526101208401526101409081840152825281610160810110610160830167ffffffffffffffff10176116b057610160820160405263ffffffff82516020840120917f1950b0dfc42b437b752641a63d09bd2a7d858914bdd31db6ef0dc3e5b2bf544e6101808501528188166101a0850152166101c08301526101e08201526080610160820152610160810161020082011067ffffffffffffffff610200830111176116b0576102008101604052610160810151610180820120907f4bdffc2f7bddf42b258c51d780c4f8e7b01dc4545bb76c1d6095e138a1df4c9b907f0000000000000000000000008d871ef2826ac9001fb2e33fdd6379b6aabf449c30147f000000000000000000000000000000000000000000000000000000000000210546141615612ab0575b50671901000000000000600052601a52603a5260ff6042601820936000603a5260405194600052166020526040526060526020600160806000825afa51903d15612aa25760006060528060405260208160048173ffffffffffffffffffffffffffffffffffffffff6123f68961201d565b167f238ac9330000000000000000000000000000000000000000000000000000000082525afa9081156109b757600091612a3f575b5073ffffffffffffffffffffffffffffffffffffffff809116911603612a15576001549163ffffffff600181851601167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000084161760015563ffffffff83166000526004602052604060002073ffffffffffffffffffffffffffffffffffffffff6124b48361201d565b167fffffffffffffffffffffffff00000000000000000000000000000000000000008254161781556125346124eb6020840161203e565b82547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b77ffffffff000000000000000000000000000000000000000016178255565b6125906125436040840161203e565b82547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7bffffffff00000000000000000000000000000000000000000000000016178255565b6125ec61259f6060840161203e565b82547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660e09190911b7fffffffff0000000000000000000000000000000000000000000000000000000016178255565b6126ba6001820161ffff6126026080860161204f565b167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000082541617815561267261263960a0860161205e565b82547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000ffff1660109190911b67ffffffffffff000016178255565b61267e60c0850161203e565b7fffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffff6bffffffff000000000000000083549260401b169116179055565b6126c760e0830183612071565b67ffffffffffffffff81116116b05760028301916126e9826114fe8554611e1a565b600090601f831160011461296f5760039493929160009183612964575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b92861b1c19161790555b0194612747610100830183612071565b67ffffffffffffffff81989298116116b0576127678161157e8454611e1a565b6000601f82116001146128ba57819063ffffffff98996000926128af575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790555b600052600660205260406000208484167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008254161790558383167f04344ed7a67fec80c444d56ee1cee242f3f75b91fecc8dbce8890069c82eb48e600080a273ffffffffffffffffffffffffffffffffffffffff6128348261201d565b166000526005602052836040600020541684831611612857575b50501690600090565b61287573ffffffffffffffffffffffffffffffffffffffff9161201d565b1660005282604060002091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000825416179055388061284e565b013590503880612785565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216908360005260206000209160005b81811061294c57509983929160019463ffffffff9b9c10612914575b505050811b0190556127b7565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88560031b161c19910135169055388080612907565b9192602060018192868f0135815501940192016128eb565b013590503880612706565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316918460005260206000209260005b8181106129fd5750916001939185600398979694106129c7575b505050811b019055612737565b01357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83881b60f8161c191690553880806129ba565b919360206001819287870135815501950192016129a0565b60046040517f10c74b03000000000000000000000000000000000000000000000000000000008152fd5b90506020813d602011612a9a575b81612a5a60209383611c9f565b8101031261016e575173ffffffffffffffffffffffffffffffffffffffff8116810361016e5773ffffffffffffffffffffffffffffffffffffffff61242b565b3d9150612a4d565b638baa579f6000526004601cfd5b60a09150807f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610200809301527fe7f0b77df5c5e5c7a7bc3e252dd153438c3d764ef75b93d0f9e5658cfd0afde36102208201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc66102408201524661026082015230610280820152012038612385565b60046040517f0819bdcd000000000000000000000000000000000000000000000000000000008152fd5b97506001969550505050505056fea264697066735822122095589eb4b5ab0766de415e6daf4ff18a4ce5190aac1c3052b77c63cb77e91b7464736f6c63430008130033
Deployed ByteCode Sourcemap
576:10455:3:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;1813:10;576:10455;;;;;;;;;;;;;;;;;;5256:49:2;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;4568:20:2;576:10455:3;;;;;;;;;;;;;;;;;;;9108:10:4;576:10455:3;;;;;;;;;;9108:10:4;9170:27;576:10455:3;9170:27:4;;576:10455:3;;;;;;;;;;;;;;;4157:10:2;576:10455:3;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;7365:13:1;;576:10455:3;;;;7416:4:1;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3195:52;576:10455;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2989:7:4;576:10455:3;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;5409:52:2;576:10455:3;;;5409:52:2;;;;576:10455:3;;;;;5409:52:2;576:10455:3;5409:52:2;576:10455:3;5409:52:2;;;;576:10455:3;:::i;:::-;;;;:::i;:::-;;;;5409:52:2;;;;;576:10455:3;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;6206:10:4;;;;576:10455:3;;;;;;;;;;;;;;;;;6228:17:4;6197:48;6193:101;;576:10455:3;11914:1349:2;576:10455:3;11914:1349:2;;;;;576:10455:3;;11914:1349:2;;;;;;;;;;;13326:22;13322:82;;11914:1349;;576:10455:3;11914:1349:2;;13470:15;;:29;13466:90;;11914:1349;13627:47;11914:1349;;;;;;;13627:47;;:::i;:::-;576:10455:3;;;13741:28:2;;13737:84;;11914:1349;576:10455:3;11914:1349:2;13877:23;;13873:84;;14103:561;;;;;;;;;14739:11;:28;14735:84;;576:10455:3;;;14930:36:2;;576:10455:3;11914:1349:2;576:10455:3;;;;;;;;;;14930:36:2;;;;;:::i;:::-;576:10455:3;14920:47:2;;576:10455:3;;;;;;;14103:561:2;576:10455:3;;;;;14103:561:2;;;;15043:39;;15039:96;;576:10455:3;;;;;;;;14103:561:2;;576:10455:3;;;;;;;;;;;;;11914:1349:2;576:10455:3;15384:48:2;;576:10455:3;;;;;;;;;6206:10:4;576:10455:3;;;;;15384:48:2;;;;;:::i;:::-;576:10455:3;15374:59:2;;576:10455:3;;;6228:17:4;576:10455:3;;;;;;;;15443:78:2;;576:10455:3;;6228:17:4;576:10455:3;;;;;14103:561:2;576:10455:3;;;;;;;15671:9:2;576:10455:3;14103:561:2;;576:10455:3;;;;;11914:1349:2;;;15690:150;;;;576:10455:3;;;;15690:150:2;;;;;576:10455:3;15690:150:2;;11914:1349;576:10455:3;;15690:150:2;;576:10455:3;;;;;14103:561:2;576:10455:3;14103:561:2;576:10455:3;;;;;6206:10:4;576:10455:3;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;15690:150:2;11914:1349;;;15690:150;;;576:10455:3;15690:150:2;;;;;;;;;;576:10455:3;14103:561:2;11914:1349;4157:10;14103:561;576:10455:3;14103:561:2;;15870:9;;576:10455:3;;;16560:30:2;16556:86;;16693:49;11914:1349;6206:10:4;576:10455:3;;16693:49:2;;;576:10455:3;16556:86:2;576:10455:3;;;16613:18:2;;;;15690:150;;;;;;:::i;:::-;576:10455:3;;11914:1349:2;15690:150;;;576:10455:3;;;;;;;;;;;;;;;;;;;15443:78:2;576:10455:3;;;15488:22:2;;;;15039:96;576:10455:3;;;15105:19:2;;;;14735:84;576:10455:3;;;14790:18:2;;;;13873:84;576:10455:3;;;13923:23:2;;;;13737:84;576:10455:3;;;13792:18:2;;;;13466:90;576:10455:3;;;13522:23:2;;;;13322:82;576:10455:3;;;13371:22:2;;;;6193:101:4;576:10455:3;;;6268:15:4;;;;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7045:34:4;7041:110;;7328:15;7219:21;7328:15;576:10455:3;;7328:15:4;576:10455:3;;;;;;;;;;;7328:15:4;576:10455:3;7328:15:4;576:10455:3;;;7399:57:4;576:10455:3;7420:10:4;7399:57;;576:10455:3;7041:110:4;576:10455:3;;;;;;7102:38:4;;;;576:10455:3;7102:38:4;;576:10455:3;;;;;;:::i;:::-;7102:38:4;576:10455:3;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;6206:10:4;;;;576:10455:3;;;;;;;;;;;;;;;;;6228:17:4;6197:48;6193:101;;10355:56:3;576:10455;;;;;;;;;;;10355:56;;:::i;:::-;10541:16;;;10567:311;;;1434:7;10567:311;;576:10455;11914:1349:2;;;;;576:10455:3;;11914:1349:2;;;;;;;-1:-1:-1;576:10455:3;11914:1349:2;;;;13326:22;13322:82;;576:10455:3;11914:1349:2;;;;13470:15;;:29;13466:90;;576:10455:3;13627:47:2;11914:1349;;;;;;;13627:47;;:::i;:::-;576:10455:3;;;13741:28:2;;13737:84;;11914:1349;576:10455:3;11914:1349:2;13877:23;;13873:84;;14103:561;;;;;;;;;14739:11;:28;14735:84;;576:10455:3;;;;;;14930:36:2;;576:10455:3;;;;;;;14930:36:2;;;;;:::i;:::-;576:10455:3;;;14930:36:2;;14920:47;576:10455:3;;;;;;;14103:561:2;576:10455:3;;;;;14103:561:2;;;;15043:39;;15039:96;;576:10455:3;;;;;;;;14103:561:2;;576:10455:3;;;;;;;;;;;;;;;15384:48:2;;576:10455:3;;;;;;;;;6206:10:4;576:10455:3;;;;;15384:48:2;;;;;:::i;:::-;576:10455:3;15374:59:2;;576:10455:3;;;6228:17:4;576:10455:3;;;;;;;;15443:78:2;;576:10455:3;;6228:17:4;576:10455:3;;;;;14103:561:2;576:10455:3;;;;;;;15671:9:2;576:10455:3;14103:561:2;;576:10455:3;;;;;11914:1349:2;;;15690:150;;;;576:10455:3;;;;15690:150:2;;;;;576:10455:3;15690:150:2;;576:10455:3;;;15690:150:2;;576:10455:3;;;;;14103:561:2;576:10455:3;14103:561:2;576:10455:3;;;;;6206:10:4;576:10455:3;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;15690:150:2;11914:1349;;;15690:150;;;576:10455:3;15690:150:2;;;;;;;;;;10567:311:3;14103:561:2;;576:10455:3;4157:10:2;14103:561;576:10455:3;14103:561:2;;15870:9;;576:10455:3;;;;16560:30:2;16556:86;;16693:49;576:10455:3;6206:10:4;576:10455:3;;16693:49:2;;;576:10455:3;15690:150:2;4157:10;15690:150;;;;;;;:::i;:::-;576:10455:3;;15690:150:2;;;;;;10567:311:3;1813:10;10567:311;;;576:10455;;;;;;;;;;;;;;;3441:52;576:10455;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;18061:4:2;576:10455:3;;;;;;;;;18061:4:2;576:10455:3;;;;;18423:15:2;;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18469:10:2;576:10455:3;;;;;;18423:15:2;;576:10455:3;;;;18152:578:2;;576:10455:3;;18152:578:2;;576:10455:3;;;18152:578:2;;576:10455:3;;;18152:578:2;;576:10455:3;;;18152:578:2;;576:10455:3;;;18152:578:2;;576:10455:3;;18152:578:2;;576:10455:3;;;;:::i;:::-;18152:578:2;;;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;18152:578:2;576:10455:3;;;;;;;;;;;18061:4:2;576:10455:3;;;;;;;;;;;;18152:578:2;;576:10455:3;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;;;;;;;;;;;;;;;;18779:35:2;576:10455:3;18779:35:2;;576:10455:3;;;;;;;;-1:-1:-1;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;18152:578:2;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18061:4:2;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1434:7;576:10455;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;19235:10:2;19196:49;19192:109;;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;;;;;19424:37:2;;576:10455:3;19424:37:2;;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;19192:109:2;576:10455:3;;;19268:22:2;;;;576:10455:3;;;;;;;;;;;;;;;4695:45:2;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;7901:10:4;576:10455:3;;;;;;;;;;;;;;;;;;;;;8107:21:4;8092:36;;8088:106;;576:10455:3;;;;;;;2989:7:4;576:10455:3;;8478:15:4;:29;8474:101;;-1:-1:-1;576:10455:3;;8639:17:4;576:10455:3;;;7901:10:4;8779:25;576:10455:3;;8779:25:4;576:10455:3;8474:101:4;576:10455:3;;;;8530:34:4;;;;576:10455:3;8530:34:4;;576:10455:3;8530:34:4;8088:106;8151:32;576:10455:3;;8151:32:4;;;;576:10455:3;8151:32:4;;576:10455:3;;;;;;;;;;:::o;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;-1:-1:-1;576:10455:3;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;-1:-1:-1;576:10455:3;;;;-1:-1:-1;576:10455:3;;;-1:-1:-1;576:10455:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;:::o;:::-;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;576:10455:3;;-1:-1:-1;576:10455:3;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:::o;:::-;;;-1:-1:-1;576:10455:3;;;;19774:590:2;576:10455:3;;;;19936:11:2;;19932:50;;20305:15;20346:1;20305:15;;;576:10455:3;;;;;;19774:590:2;:::o;19932:50::-;19963:8;;;19970:1;19963:8;:::o;576:10455:3:-;;;;;;;;;;;;;;;-1:-1:-1;576:10455:3;;;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;1940:189::-;;576:10455;;;;;;;1940:189;:::o;:::-;;576:10455;;;;;;;1940:189;:::o;:::-;;576:10455;;;;;;;1940:189;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;5664:3289::-;;;;;;;;5999:9;;;:::i;:::-;576:10455;;;5988:28;;;576:10455;;;;;;;;;;;;5988:28;;;;;:::i;:::-;576:10455;5978:39;;576:10455;;-1:-1:-1;576:10455:3;6051:18;5988:28;576:10455;;;-1:-1:-1;576:10455:3;;;6270:19;6266:79;;576:10455;;;;;6470:15;576:10455;6463:33;;6459:89;;7059:9;;;:::i;:::-;7098:12;;5988:28;7098:12;;;:::i;:::-;7140:10;576:10455;7140:10;;;:::i;:::-;7180:13;576:10455;7180:13;;;:::i;:::-;7223:14;;;;;:::i;:::-;7267:15;;;;;:::i;:::-;7312;;;;;;:::i;:::-;7424;;;;;;;:::i;:::-;576:10455;;;;;:::i;:::-;;;;5988:28;576:10455;7408:33;7481:10;;;;;;;;:::i;:::-;576:10455;;;;;:::i;:::-;;;;5988:28;576:10455;7471:21;576:10455;;;6961:557;5988:28;6961:557;;1940:189;576:10455;;;;;1940:189;;576:10455;;;;1940:189;;576:10455;;;7223:14;1940:189;;576:10455;;;7267:15;1940:189;;576:10455;;;7312:15;1940:189;;576:10455;;;7424:15;1940:189;;576:10455;;;7481:10;1940:189;;576:10455;1940:189;;;576:10455;1940:189;;;;;576:10455;6961:557;;576:10455;1940:189;576:10455;;;1940:189;576:10455;;;-1:-1:-1;576:10455:3;;;1940:189;576:10455;;;;;;;5988:28;6961:557;;6926:614;6731:827;2576:254;6731:827;;;576:10455;;;;1940:189;;;576:10455;;1940:189;;;576:10455;1940:189;;;576:10455;7223:14;1940:189;576:10455;;6731:827;1940:189;576:10455;;;;;;;;;;;;;;;;;;;1940:189;576:10455;;;6731:827;;;6636:936;5840:17:1;5997:22;8980:11;;9044:111;;8935:14;9044:111;;;;6033:76;;5664:3289:3;6172:401:1;;-1:-1:-1;6172:401:1;;;;;6548:1013:0;6172:401:1;;;;-1:-1:-1;6172:401:1;;576:10455:3;6548:1013:0;;-1:-1:-1;6548:1013:0;;5988:28:3;6548:1013:0;576:10455:3;6548:1013:0;576:10455:3;6548:1013:0;5988:28:3;6548:1013:0;7223:14:3;-1:-1:-1;6548:1013:0;;;;;;;;;-1:-1:-1;576:10455:3;6548:1013:0;;576:10455:3;6548:1013:0;5988:28:3;7896:9;7886:29;7896:9;576:10455;7896:9;;;:::i;:::-;576:10455;;7886:29;;;;;;;;;-1:-1:-1;7886:29:3;;;5664:3289;576:10455;;;;;;;8006:34;8002:88;;6548:1013:0;576:10455:3;;;6548:1013:0;576:10455:3;;;;;;;;;6548:1013:0;576:10455:3;;;;-1:-1:-1;576:10455:3;7886:29;5988:28;576:10455;;-1:-1:-1;576:10455:3;;1940:189;;;:::i;:::-;576:10455;;;;;;;;1940:189;;5988:28;7098:12;;1940:189;:::i;:::-;576:10455;;;;;;;;;;;;;;;1940:189;;;576:10455;7140:10;;1940:189;:::i;:::-;576:10455;;;;;;;;;;;;;;;1940:189;;;576:10455;7180:13;;1940:189;:::i;:::-;576:10455;;;;;;;;;;;;;;;1940:189;;6548:1013:0;1940:189:3;;576:10455;1940:189;7223:14;;;1940:189;:::i;:::-;576:10455;;;;;;;;1940:189;;7267:15;;;1940:189;:::i;:::-;576:10455;;;;;;;;;;;;;;;1940:189;;7312:15;;;1940:189;:::i;:::-;576:10455;;;;;;;;;;;;;;1940:189;;7424:15;;;1940:189;;:::i;:::-;576:10455;1940:189;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;1940:189:3;;;;;;;;;;;;;-1:-1:-1;;1940:189:3;;;;576:10455;;;;6548:1013:0;576:10455:3;;;;;;;;1940:189;;;;7481:10;1940:189;7481:10;;;1940:189;;:::i;:::-;576:10455;1940:189;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;1940:189:3;;;;;;;;;576:10455;1940:189;;-1:-1:-1;1940:189:3;;;;576:10455;;;;6548:1013:0;576:10455:3;;1940:189;576:10455;;;;;1940:189;;;-1:-1:-1;576:10455:3;6051:18;5988:28;576:10455;;-1:-1:-1;576:10455:3;;;;;;;;;;;;;;8598:35;-1:-1:-1;8598:35:3;;576:10455;8788:9;;;:::i;:::-;576:10455;-1:-1:-1;576:10455:3;8769:18;5988:28;576:10455;;;-1:-1:-1;576:10455:3;;;;;;8761:37;8757:105;;1940:189;576:10455;;;8916:30;-1:-1:-1;5664:3289:3;:::o;8757:105::-;8833:9;576:10455;8833:9;;:::i;:::-;576:10455;-1:-1:-1;576:10455:3;;;-1:-1:-1;576:10455:3;;;;;;;;;;8757:105;;;;1940:189;;;;-1:-1:-1;1940:189:3;;;;;5988:28;1940:189;;576:10455;;-1:-1:-1;576:10455:3;5988:28;-1:-1:-1;576:10455:3;1940:189;-1:-1:-1;1940:189:3;;;;;;;;;;;6548:1013:0;1940:189:3;576:10455;1940:189;;;;;;;;;;;;;;;;;576:10455;;;1940:189;576:10455;;;;1940:189;;;576:10455;1940:189;;;;;;;;;;5988:28;6548:1013:0;1940:189:3;;;;;;;;;;;;;;;;;;;-1:-1:-1;1940:189:3;;;;;5988:28;1940:189;;576:10455;;-1:-1:-1;576:10455:3;5988:28;-1:-1:-1;576:10455:3;1940:189;-1:-1:-1;1940:189:3;;;;;;;;6548:1013:0;1940:189:3;;;;;;;;;;;;;;;;;;;;;;;;;576:10455;;;;;;;;;1940:189;;;;;;;;;;5988:28;6548:1013:0;1940:189:3;;;;;;;;;;;;;;;8002:88;7886:29;576:10455;;8063:16;;;;7886:29;;;5988:28;7886:29;;5988:28;7886:29;;;;;;5988:28;7886:29;;;:::i;:::-;;;1940:189;;;;;576:10455;;;;;;;;7886:29;;;;;-1:-1:-1;7886:29:3;;6548:1013:0;;-1:-1:-1;6548:1013:0;;;;6033:76:1;7267:15:3;576:10455;;;8388:347:1;576:10455:3;;;;8388:347:1;8264:15;8388:347;;;;8307:18;8388:347;;;;9044:111;8388:347;;;;9044:111;8388:347;;;;576:10455:3;8388:347:1;6033:76;;;6459:89:3;6519:18;576:10455;;6519:18;;;;6266:79;6305:29;-1:-1:-1;6329:4:3;;6305:29;-1:-1:-1;;;;;;6305:29:3:o
Swarm Source
ipfs://95589eb4b5ab0766de415e6daf4ff18a4ce5190aac1c3052b77c63cb77e91b74
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.