Source Code
Overview
XDC Balance
XDC Value
$0.00Latest 25 from a total of 365 transactions
| Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Flip Coin | 95055578 | 21 days ago | IN | 0 XDC | 0.0027721 | ||||
| Flip Coin | 95055396 | 21 days ago | IN | 0 XDC | 0.00370927 | ||||
| Flip Coin | 94780962 | 29 days ago | IN | 0 XDC | 0.0028254 | ||||
| Flip Coin | 94234600 | 43 days ago | IN | 0 XDC | 0.0029604 | ||||
| Flip Coin | 93995703 | 48 days ago | IN | 0 XDC | 0.0028246 | ||||
| Flip Coin | 93865206 | 52 days ago | IN | 0 XDC | 0.0028254 | ||||
| Flip Coin | 93798642 | 53 days ago | IN | 0 XDC | 0.0029604 | ||||
| Flip Coin | 93798601 | 53 days ago | IN | 0 XDC | 0.0027729 | ||||
| Flip Coin | 93798579 | 53 days ago | IN | 0 XDC | 0.00282507 | ||||
| Flip Coin | 93797405 | 53 days ago | IN | 0 XDC | 0.0029604 | ||||
| Flip Coin | 93797359 | 53 days ago | IN | 0 XDC | 0.0028254 | ||||
| Flip Coin | 93797342 | 53 days ago | IN | 0 XDC | 0.0029604 | ||||
| Flip Coin | 93797238 | 53 days ago | IN | 0 XDC | 0.0029604 | ||||
| Flip Coin | 93797220 | 53 days ago | IN | 0 XDC | 0.0028254 | ||||
| Flip Coin | 93795457 | 53 days ago | IN | 0 XDC | 0.00290677 | ||||
| Flip Coin | 93795429 | 53 days ago | IN | 0 XDC | 0.00370927 | ||||
| Flip Coin | 93792945 | 54 days ago | IN | 0 XDC | 0.00282427 | ||||
| Flip Coin | 93717681 | 55 days ago | IN | 0 XDC | 0.0027729 | ||||
| Flip Coin | 93717563 | 55 days ago | IN | 0 XDC | 0.0027729 | ||||
| Flip Coin | 93548159 | 60 days ago | IN | 0 XDC | 0.0028254 | ||||
| Flip Coin | 93357994 | 64 days ago | IN | 0 XDC | 0.0029596 | ||||
| Flip Coin | 93357918 | 64 days ago | IN | 0 XDC | 0.0028246 | ||||
| Recover Own Stuc... | 93357893 | 64 days ago | IN | 0 XDC | 0.00125058 | ||||
| Flip Coin | 93267298 | 67 days ago | IN | 0 XDC | 0.0028246 | ||||
| Flip Coin | 93267220 | 67 days ago | IN | 0 XDC | 0.0028254 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Contract Name:
Flip
Compiler Version
v0.8.20+commit.a1b79de6
Contract Source Code (Solidity)
/**
*Submitted for verification at xdcscan.com on 2025-08-04
*/
// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
// File: @openzeppelin/contracts/utils/Context.sol
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// File: @openzeppelin/contracts/utils/Pausable.sol
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/PliTokenInterface.sol
pragma solidity ^0.8.0;
interface PliTokenInterface {
function allowance(address owner, address spender) external view returns (uint256 remaining);
function approve(address spender, uint256 value) external returns (bool success);
function balanceOf(address owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 decimalPlaces);
function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
function increaseApproval(address spender, uint256 subtractedValue) external;
function name() external view returns (string memory tokenName);
function symbol() external view returns (string memory tokenSymbol);
function totalSupply() external view returns (uint256 totalTokensIssued);
function transfer(address to, uint256 value) external returns (bool success);
function transferAndCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool success);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool success);
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/BlockhashStoreInterface.sol
pragma solidity ^0.8.0;
interface BlockhashStoreInterface {
function getBlockhash(uint256 number) external view returns (bytes32);
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/AggregatorV3Interface.sol
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol
pragma solidity ^0.8.0;
interface VRFCoordinatorV2Interface {
/**
* @notice Get configuration relevant for making requests
* @return minimumRequestConfirmations global min for request confirmations
* @return maxGasLimit global max for request gas limit
* @return s_provingKeyHashes list of registered key hashes
*/
function getRequestConfig()
external
view
returns (
uint16,
uint32,
bytes32[] memory
);
/**
* @notice Request a set of random words.
* @param keyHash - Corresponds to a particular oracle job which uses
* that key for generating the VRF proof. Different keyHash's have different gas price
* ceilings, so you can select a specific one to bound your maximum per request cost.
* @param subId - The ID of the VRF subscription. Must be funded
* with the minimum subscription balance required for the selected keyHash.
* @param minimumRequestConfirmations - How many blocks you'd like the
* oracle to wait before responding to the request. See SECURITY CONSIDERATIONS
* for why you may want to request more. The acceptable range is
* [minimumRequestBlockConfirmations, 200].
* @param callbackGasLimit - How much gas you'd like to receive in your
* fulfillRandomWords callback. Note that gasleft() inside fulfillRandomWords
* may be slightly less than this amount because of gas used calling the function
* (argument decoding etc.), so you may need to request slightly more than you expect
* to have inside fulfillRandomWords. The acceptable range is
* [0, maxGasLimit]
* @param numWords - The number of uint256 random values you'd like to receive
* in your fulfillRandomWords callback. Note these numbers are expanded in a
* secure way by the VRFCoordinator from a single random value supplied by the oracle.
* @return requestId - A unique identifier of the request. Can be used to match
* a request to a response in fulfillRandomWords.
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external returns (uint256 requestId);
/**
* @notice Create a VRF subscription.
* @return subId - A unique subscription id.
* @dev You can manage the consumer set dynamically with addConsumer/removeConsumer.
* @dev Note to fund the subscription, use transferAndCall. For example
* @dev PLITOKEN.transferAndCall(
* @dev address(COORDINATOR),
* @dev amount,
* @dev abi.encode(subId));
*/
function createSubscription() external returns (uint64 subId);
/**
* @notice Get a VRF subscription.
* @param subId - ID of the subscription
* @return balance - PLI balance of the subscription in juels.
* @return reqCount - number of requests for this subscription, determines fee tier.
* @return owner - owner of the subscription.
* @return consumers - list of consumer address which are able to use this subscription.
*/
function getSubscription(uint64 subId)
external
view
returns (
uint96 balance,
uint64 reqCount,
address owner,
address[] memory consumers
);
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @param newOwner - proposed new owner of the subscription
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner) external;
/**
* @notice Request subscription owner transfer.
* @param subId - ID of the subscription
* @dev will revert if original owner of subId has
* not requested that msg.sender become the new owner.
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external;
/**
* @notice Add a consumer to a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - New consumer which can use the subscription
*/
function addConsumer(uint64 subId, address consumer) external;
/**
* @notice Remove a consumer from a VRF subscription.
* @param subId - ID of the subscription
* @param consumer - Consumer to remove from the subscription
*/
function removeConsumer(uint64 subId, address consumer) external;
/**
* @notice Cancel a subscription
* @param subId - ID of the subscription
* @param to - Where to send the remaining PLI to
*/
function cancelSubscription(uint64 subId, address to) external;
/*
* @notice Check to see if there exists a request commitment consumers
* for all consumers and keyhashes for a given sub.
* @param subId - ID of the subscription
* @return true if there exists at least one unfulfilled request for the subscription, false
* otherwise.
*/
function pendingRequestExists(uint64 subId) external view returns (bool);
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/TypeAndVersionInterface.sol
pragma solidity ^0.8.0;
abstract contract TypeAndVersionInterface {
function typeAndVersion() external pure virtual returns (string memory);
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/ERC677ReceiverInterface.sol
pragma solidity ^0.8.6;
interface ERC677ReceiverInterface {
function onTokenTransfer(
address sender,
uint256 amount,
bytes calldata data
) external;
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/VRF.sol
pragma solidity ^0.8.0;
/** ****************************************************************************
* @notice Verification of verifiable-random-function (VRF) proofs, following
* @notice https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3
* @notice See https://eprint.iacr.org/2017/099.pdf for security proofs.
* @dev Bibliographic references:
* @dev Goldberg, et al., "Verifiable Random Functions (VRFs)", Internet Draft
* @dev draft-irtf-cfrg-vrf-05, IETF, Aug 11 2019,
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05
* @dev Papadopoulos, et al., "Making NSEC5 Practical for DNSSEC", Cryptology
* @dev ePrint Archive, Report 2017/099, https://eprint.iacr.org/2017/099.pdf
* ****************************************************************************
* @dev USAGE
* @dev The main entry point is randomValueFromVRFProof. See its docstring.
* ****************************************************************************
* @dev PURPOSE
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is computationally indistinguishable to her from a uniform
* @dev random sample from the output space.
* @dev The purpose of this contract is to perform that verification.
* ****************************************************************************
* @dev DESIGN NOTES
* @dev The VRF algorithm verified here satisfies the full uniqueness, full
* @dev collision resistance, and full pseudo-randomness security properties.
* @dev See "SECURITY PROPERTIES" below, and
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-3
* @dev An elliptic curve point is generally represented in the solidity code
* @dev as a uint256[2], corresponding to its affine coordinates in
* @dev GF(FIELD_SIZE).
* @dev For the sake of efficiency, this implementation deviates from the spec
* @dev in some minor ways:
* @dev - Keccak hash rather than the SHA256 hash recommended in
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5
* @dev Keccak costs much less gas on the EVM, and provides similar security.
* @dev - Secp256k1 curve instead of the P-256 or ED25519 curves recommended in
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5
* @dev For curve-point multiplication, it's much cheaper to abuse ECRECOVER
* @dev - hashToCurve recursively hashes until it finds a curve x-ordinate. On
* @dev the EVM, this is slightly more efficient than the recommendation in
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1
* @dev step 5, to concatenate with a nonce then hash, and rehash with the
* @dev nonce updated until a valid x-ordinate is found.
* @dev - hashToCurve does not include a cipher version string or the byte 0x1
* @dev in the hash message, as recommended in step 5.B of the draft
* @dev standard. They are unnecessary here because no variation in the
* @dev cipher suite is allowed.
* @dev - Similarly, the hash input in scalarFromCurvePoints does not include a
* @dev commitment to the cipher suite, either, which differs from step 2 of
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3
* @dev . Also, the hash input is the concatenation of the uncompressed
* @dev points, not the compressed points as recommended in step 3.
* @dev - In the calculation of the challenge value "c", the "u" value (i.e.
* @dev the value computed by Reggie as the nonce times the secp256k1
* @dev generator point, see steps 5 and 7 of
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.3
* @dev ) is replaced by its ethereum address, i.e. the lower 160 bits of the
* @dev keccak hash of the original u. This is because we only verify the
* @dev calculation of u up to its address, by abusing ECRECOVER.
* ****************************************************************************
* @dev SECURITY PROPERTIES
* @dev Here are the security properties for this VRF:
* @dev Full uniqueness: For any seed and valid VRF public key, there is
* @dev exactly one VRF output which can be proved to come from that seed, in
* @dev the sense that the proof will pass verifyVRFProof.
* @dev Full collision resistance: It's cryptographically infeasible to find
* @dev two seeds with same VRF output from a fixed, valid VRF key
* @dev Full pseudorandomness: Absent the proofs that the VRF outputs are
* @dev derived from a given seed, the outputs are computationally
* @dev indistinguishable from randomness.
* @dev https://eprint.iacr.org/2017/099.pdf, Appendix B contains the proofs
* @dev for these properties.
* @dev For secp256k1, the key validation described in section
* @dev https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.6
* @dev is unnecessary, because secp256k1 has cofactor 1, and the
* @dev representation of the public key used here (affine x- and y-ordinates
* @dev of the secp256k1 point on the standard y^2=x^3+7 curve) cannot refer to
* @dev the point at infinity.
* ****************************************************************************
* @dev OTHER SECURITY CONSIDERATIONS
*
* @dev The seed input to the VRF could in principle force an arbitrary amount
* @dev of work in hashToCurve, by requiring extra rounds of hashing and
* @dev checking whether that's yielded the x ordinate of a secp256k1 point.
* @dev However, under the Random Oracle Model the probability of choosing a
* @dev point which forces n extra rounds in hashToCurve is 2⁻ⁿ. The base cost
* @dev for calling hashToCurve is about 25,000 gas, and each round of checking
* @dev for a valid x ordinate costs about 15,555 gas, so to find a seed for
* @dev which hashToCurve would cost more than 2,017,000 gas, one would have to
* @dev try, in expectation, about 2¹²⁸ seeds, which is infeasible for any
* @dev foreseeable computational resources. (25,000 + 128 * 15,555 < 2,017,000.)
* @dev Since the gas block limit for the Ethereum main net is 10,000,000 gas,
* @dev this means it is infeasible for an adversary to prevent correct
* @dev operation of this contract by choosing an adverse seed.
* @dev (See TestMeasureHashToCurveGasCost for verification of the gas cost for
* @dev hashToCurve.)
* @dev It may be possible to make a secure constant-time hashToCurve function.
* @dev See notes in hashToCurve docstring.
*/
contract VRF {
// See https://www.secg.org/sec2-v2.pdf, section 2.4.1, for these constants.
// Number of points in Secp256k1
uint256 private constant GROUP_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
// Prime characteristic of the galois field over which Secp256k1 is defined
uint256 private constant FIELD_SIZE =
// solium-disable-next-line indentation
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
uint256 private constant WORD_LENGTH_BYTES = 0x20;
// (base^exponent) % FIELD_SIZE
// Cribbed from https://medium.com/@rbkhmrcr/precompiles-solidity-e5d29bd428c4
function bigModExp(uint256 base, uint256 exponent) internal view returns (uint256 exponentiation) {
uint256 callResult;
uint256[6] memory bigModExpContractInputs;
bigModExpContractInputs[0] = WORD_LENGTH_BYTES; // Length of base
bigModExpContractInputs[1] = WORD_LENGTH_BYTES; // Length of exponent
bigModExpContractInputs[2] = WORD_LENGTH_BYTES; // Length of modulus
bigModExpContractInputs[3] = base;
bigModExpContractInputs[4] = exponent;
bigModExpContractInputs[5] = FIELD_SIZE;
uint256[1] memory output;
assembly {
// solhint-disable-line no-inline-assembly
callResult := staticcall(
not(0), // Gas cost: no limit
0x05, // Bigmodexp contract address
bigModExpContractInputs,
0xc0, // Length of input segment: 6*0x20-bytes
output,
0x20 // Length of output segment
)
}
if (callResult == 0) {
revert("bigModExp failure!");
}
return output[0];
}
// Let q=FIELD_SIZE. q % 4 = 3, ∴ x≡r^2 mod q ⇒ x^SQRT_POWER≡±r mod q. See
// https://en.wikipedia.org/wiki/Modular_square_root#Prime_or_prime_power_modulus
uint256 private constant SQRT_POWER = (FIELD_SIZE + 1) >> 2;
// Computes a s.t. a^2 = x in the field. Assumes a exists
function squareRoot(uint256 x) internal view returns (uint256) {
return bigModExp(x, SQRT_POWER);
}
// The value of y^2 given that (x,y) is on secp256k1.
function ySquared(uint256 x) internal pure returns (uint256) {
// Curve is y^2=x^3+7. See section 2.4.1 of https://www.secg.org/sec2-v2.pdf
uint256 xCubed = mulmod(x, mulmod(x, x, FIELD_SIZE), FIELD_SIZE);
return addmod(xCubed, 7, FIELD_SIZE);
}
// True iff p is on secp256k1
function isOnCurve(uint256[2] memory p) internal pure returns (bool) {
// Section 2.3.6. in https://www.secg.org/sec1-v2.pdf
// requires each ordinate to be in [0, ..., FIELD_SIZE-1]
require(p[0] < FIELD_SIZE, "invalid x-ordinate");
require(p[1] < FIELD_SIZE, "invalid y-ordinate");
return ySquared(p[0]) == mulmod(p[1], p[1], FIELD_SIZE);
}
// Hash x uniformly into {0, ..., FIELD_SIZE-1}.
function fieldHash(bytes memory b) internal pure returns (uint256 x_) {
x_ = uint256(keccak256(b));
// Rejecting if x >= FIELD_SIZE corresponds to step 2.1 in section 2.3.4 of
// http://www.secg.org/sec1-v2.pdf , which is part of the definition of
// string_to_point in the IETF draft
while (x_ >= FIELD_SIZE) {
x_ = uint256(keccak256(abi.encodePacked(x_)));
}
}
// Hash b to a random point which hopefully lies on secp256k1. The y ordinate
// is always even, due to
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.1.1
// step 5.C, which references arbitrary_string_to_point, defined in
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.5 as
// returning the point with given x ordinate, and even y ordinate.
function newCandidateSecp256k1Point(bytes memory b) internal view returns (uint256[2] memory p) {
unchecked {
p[0] = fieldHash(b);
p[1] = squareRoot(ySquared(p[0]));
if (p[1] % 2 == 1) {
// Note that 0 <= p[1] < FIELD_SIZE
// so this cannot wrap, we use unchecked to save gas.
p[1] = FIELD_SIZE - p[1];
}
}
}
// Domain-separation tag for initial hash in hashToCurve. Corresponds to
// vrf.go/hashToCurveHashPrefix
uint256 internal constant HASH_TO_CURVE_HASH_PREFIX = 1;
// Cryptographic hash function onto the curve.
//
// Corresponds to algorithm in section 5.4.1.1 of the draft standard. (But see
// DESIGN NOTES above for slight differences.)
//
// TODO(alx): Implement a bounded-computation hash-to-curve, as described in
// "Construction of Rational Points on Elliptic Curves over Finite Fields"
// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.831.5299&rep=rep1&type=pdf
// and suggested by
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-01#section-5.2.2
// (Though we can't used exactly that because secp256k1's j-invariant is 0.)
//
// This would greatly simplify the analysis in "OTHER SECURITY CONSIDERATIONS"
// https://www.pivotaltracker.com/story/show/171120900
function hashToCurve(uint256[2] memory pk, uint256 input) internal view returns (uint256[2] memory rv) {
rv = newCandidateSecp256k1Point(abi.encodePacked(HASH_TO_CURVE_HASH_PREFIX, pk, input));
while (!isOnCurve(rv)) {
rv = newCandidateSecp256k1Point(abi.encodePacked(rv[0]));
}
}
/** *********************************************************************
* @notice Check that product==scalar*multiplicand
*
* @dev Based on Vitalik Buterin's idea in ethresear.ch post cited below.
*
* @param multiplicand: secp256k1 point
* @param scalar: non-zero GF(GROUP_ORDER) scalar
* @param product: secp256k1 expected to be multiplier * multiplicand
* @return verifies true iff product==scalar*multiplicand, with cryptographically high probability
*/
function ecmulVerify(
uint256[2] memory multiplicand,
uint256 scalar,
uint256[2] memory product
) internal pure returns (bool verifies) {
require(scalar != 0, "zero scalar"); // Rules out an ecrecover failure case
uint256 x = multiplicand[0]; // x ordinate of multiplicand
uint8 v = multiplicand[1] % 2 == 0 ? 27 : 28; // parity of y ordinate
// https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
// Point corresponding to address ecrecover(0, v, x, s=scalar*x) is
// (x⁻¹ mod GROUP_ORDER) * (scalar * x * multiplicand - 0 * g), i.e.
// scalar*multiplicand. See https://crypto.stackexchange.com/a/18106
bytes32 scalarTimesX = bytes32(mulmod(scalar, x, GROUP_ORDER));
address actual = ecrecover(bytes32(0), v, bytes32(x), scalarTimesX);
// Explicit conversion to address takes bottom 160 bits
address expected = address(uint160(uint256(keccak256(abi.encodePacked(product)))));
return (actual == expected);
}
// Returns x1/z1-x2/z2=(x1z2-x2z1)/(z1z2) in projective coordinates on P¹(𝔽ₙ)
function projectiveSub(
uint256 x1,
uint256 z1,
uint256 x2,
uint256 z2
) internal pure returns (uint256 x3, uint256 z3) {
unchecked {
uint256 num1 = mulmod(z2, x1, FIELD_SIZE);
// Note this cannot wrap since x2 is a point in [0, FIELD_SIZE-1]
// we use unchecked to save gas.
uint256 num2 = mulmod(FIELD_SIZE - x2, z1, FIELD_SIZE);
(x3, z3) = (addmod(num1, num2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE));
}
}
// Returns x1/z1*x2/z2=(x1x2)/(z1z2), in projective coordinates on P¹(𝔽ₙ)
function projectiveMul(
uint256 x1,
uint256 z1,
uint256 x2,
uint256 z2
) internal pure returns (uint256 x3, uint256 z3) {
(x3, z3) = (mulmod(x1, x2, FIELD_SIZE), mulmod(z1, z2, FIELD_SIZE));
}
/** **************************************************************************
@notice Computes elliptic-curve sum, in projective co-ordinates
@dev Using projective coordinates avoids costly divisions
@dev To use this with p and q in affine coordinates, call
@dev projectiveECAdd(px, py, qx, qy). This will return
@dev the addition of (px, py, 1) and (qx, qy, 1), in the
@dev secp256k1 group.
@dev This can be used to calculate the z which is the inverse to zInv
@dev in isValidVRFOutput. But consider using a faster
@dev re-implementation such as ProjectiveECAdd in the golang vrf package.
@dev This function assumes [px,py,1],[qx,qy,1] are valid projective
coordinates of secp256k1 points. That is safe in this contract,
because this method is only used by linearCombination, which checks
points are on the curve via ecrecover.
**************************************************************************
@param px The first affine coordinate of the first summand
@param py The second affine coordinate of the first summand
@param qx The first affine coordinate of the second summand
@param qy The second affine coordinate of the second summand
(px,py) and (qx,qy) must be distinct, valid secp256k1 points.
**************************************************************************
Return values are projective coordinates of [px,py,1]+[qx,qy,1] as points
on secp256k1, in P²(𝔽ₙ)
@return sx
@return sy
@return sz
*/
function projectiveECAdd(
uint256 px,
uint256 py,
uint256 qx,
uint256 qy
)
internal
pure
returns (
uint256 sx,
uint256 sy,
uint256 sz
)
{
unchecked {
// See "Group law for E/K : y^2 = x^3 + ax + b", in section 3.1.2, p. 80,
// "Guide to Elliptic Curve Cryptography" by Hankerson, Menezes and Vanstone
// We take the equations there for (sx,sy), and homogenize them to
// projective coordinates. That way, no inverses are required, here, and we
// only need the one inverse in affineECAdd.
// We only need the "point addition" equations from Hankerson et al. Can
// skip the "point doubling" equations because p1 == p2 is cryptographically
// impossible, and required not to be the case in linearCombination.
// Add extra "projective coordinate" to the two points
(uint256 z1, uint256 z2) = (1, 1);
// (lx, lz) = (qy-py)/(qx-px), i.e., gradient of secant line.
// Cannot wrap since px and py are in [0, FIELD_SIZE-1]
uint256 lx = addmod(qy, FIELD_SIZE - py, FIELD_SIZE);
uint256 lz = addmod(qx, FIELD_SIZE - px, FIELD_SIZE);
uint256 dx; // Accumulates denominator from sx calculation
// sx=((qy-py)/(qx-px))^2-px-qx
(sx, dx) = projectiveMul(lx, lz, lx, lz); // ((qy-py)/(qx-px))^2
(sx, dx) = projectiveSub(sx, dx, px, z1); // ((qy-py)/(qx-px))^2-px
(sx, dx) = projectiveSub(sx, dx, qx, z2); // ((qy-py)/(qx-px))^2-px-qx
uint256 dy; // Accumulates denominator from sy calculation
// sy=((qy-py)/(qx-px))(px-sx)-py
(sy, dy) = projectiveSub(px, z1, sx, dx); // px-sx
(sy, dy) = projectiveMul(sy, dy, lx, lz); // ((qy-py)/(qx-px))(px-sx)
(sy, dy) = projectiveSub(sy, dy, py, z1); // ((qy-py)/(qx-px))(px-sx)-py
if (dx != dy) {
// Cross-multiply to put everything over a common denominator
sx = mulmod(sx, dy, FIELD_SIZE);
sy = mulmod(sy, dx, FIELD_SIZE);
sz = mulmod(dx, dy, FIELD_SIZE);
} else {
// Already over a common denominator, use that for z ordinate
sz = dx;
}
}
}
// p1+p2, as affine points on secp256k1.
//
// invZ must be the inverse of the z returned by projectiveECAdd(p1, p2).
// It is computed off-chain to save gas.
//
// p1 and p2 must be distinct, because projectiveECAdd doesn't handle
// point doubling.
function affineECAdd(
uint256[2] memory p1,
uint256[2] memory p2,
uint256 invZ
) internal pure returns (uint256[2] memory) {
uint256 x;
uint256 y;
uint256 z;
(x, y, z) = projectiveECAdd(p1[0], p1[1], p2[0], p2[1]);
require(mulmod(z, invZ, FIELD_SIZE) == 1, "invZ must be inverse of z");
// Clear the z ordinate of the projective representation by dividing through
// by it, to obtain the affine representation
return [mulmod(x, invZ, FIELD_SIZE), mulmod(y, invZ, FIELD_SIZE)];
}
// True iff address(c*p+s*g) == lcWitness, where g is generator. (With
// cryptographically high probability.)
function verifyLinearCombinationWithGenerator(
uint256 c,
uint256[2] memory p,
uint256 s,
address lcWitness
) internal pure returns (bool) {
// Rule out ecrecover failure modes which return address 0.
unchecked {
require(lcWitness != address(0), "bad witness");
uint8 v = (p[1] % 2 == 0) ? 27 : 28; // parity of y-ordinate of p
// Note this cannot wrap (X - Y % X), but we use unchecked to save
// gas.
bytes32 pseudoHash = bytes32(GROUP_ORDER - mulmod(p[0], s, GROUP_ORDER)); // -s*p[0]
bytes32 pseudoSignature = bytes32(mulmod(c, p[0], GROUP_ORDER)); // c*p[0]
// https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
// The point corresponding to the address returned by
// ecrecover(-s*p[0],v,p[0],c*p[0]) is
// (p[0]⁻¹ mod GROUP_ORDER)*(c*p[0]-(-s)*p[0]*g)=c*p+s*g.
// See https://crypto.stackexchange.com/a/18106
// https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v
address computed = ecrecover(pseudoHash, v, bytes32(p[0]), pseudoSignature);
return computed == lcWitness;
}
}
// c*p1 + s*p2. Requires cp1Witness=c*p1 and sp2Witness=s*p2. Also
// requires cp1Witness != sp2Witness (which is fine for this application,
// since it is cryptographically impossible for them to be equal. In the
// (cryptographically impossible) case that a prover accidentally derives
// a proof with equal c*p1 and s*p2, they should retry with a different
// proof nonce.) Assumes that all points are on secp256k1
// (which is checked in verifyVRFProof below.)
function linearCombination(
uint256 c,
uint256[2] memory p1,
uint256[2] memory cp1Witness,
uint256 s,
uint256[2] memory p2,
uint256[2] memory sp2Witness,
uint256 zInv
) internal pure returns (uint256[2] memory) {
unchecked {
// Note we are relying on the wrap around here
require((cp1Witness[0] % FIELD_SIZE) != (sp2Witness[0] % FIELD_SIZE), "points in sum must be distinct");
require(ecmulVerify(p1, c, cp1Witness), "First mul check failed");
require(ecmulVerify(p2, s, sp2Witness), "Second mul check failed");
return affineECAdd(cp1Witness, sp2Witness, zInv);
}
}
// Domain-separation tag for the hash taken in scalarFromCurvePoints.
// Corresponds to scalarFromCurveHashPrefix in vrf.go
uint256 internal constant SCALAR_FROM_CURVE_POINTS_HASH_PREFIX = 2;
// Pseudo-random number from inputs. Matches vrf.go/scalarFromCurvePoints, and
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-05#section-5.4.3
// The draft calls (in step 7, via the definition of string_to_int, in
// https://datatracker.ietf.org/doc/html/rfc8017#section-4.2 ) for taking the
// first hash without checking that it corresponds to a number less than the
// group order, which will lead to a slight bias in the sample.
//
// TODO(alx): We could save a bit of gas by following the standard here and
// using the compressed representation of the points, if we collated the y
// parities into a single bytes32.
// https://www.pivotaltracker.com/story/show/171120588
function scalarFromCurvePoints(
uint256[2] memory hash,
uint256[2] memory pk,
uint256[2] memory gamma,
address uWitness,
uint256[2] memory v
) internal pure returns (uint256 s) {
return uint256(keccak256(abi.encodePacked(SCALAR_FROM_CURVE_POINTS_HASH_PREFIX, hash, pk, gamma, v, uWitness)));
}
// True if (gamma, c, s) is a correctly constructed randomness proof from pk
// and seed. zInv must be the inverse of the third ordinate from
// projectiveECAdd applied to cGammaWitness and sHashWitness. Corresponds to
// section 5.3 of the IETF draft.
//
// TODO(alx): Since I'm only using pk in the ecrecover call, I could only pass
// the x ordinate, and the parity of the y ordinate in the top bit of uWitness
// (which I could make a uint256 without using any extra space.) Would save
// about 2000 gas. https://www.pivotaltracker.com/story/show/170828567
function verifyVRFProof(
uint256[2] memory pk,
uint256[2] memory gamma,
uint256 c,
uint256 s,
uint256 seed,
address uWitness,
uint256[2] memory cGammaWitness,
uint256[2] memory sHashWitness,
uint256 zInv
) internal view {
unchecked {
require(isOnCurve(pk), "public key is not on curve");
require(isOnCurve(gamma), "gamma is not on curve");
require(isOnCurve(cGammaWitness), "cGammaWitness is not on curve");
require(isOnCurve(sHashWitness), "sHashWitness is not on curve");
// Step 5. of IETF draft section 5.3 (pk corresponds to 5.3's Y, and here
// we use the address of u instead of u itself. Also, here we add the
// terms instead of taking the difference, and in the proof construction in
// vrf.GenerateProof, we correspondingly take the difference instead of
// taking the sum as they do in step 7 of section 5.1.)
require(verifyLinearCombinationWithGenerator(c, pk, s, uWitness), "addr(c*pk+s*g)!=_uWitness");
// Step 4. of IETF draft section 5.3 (pk corresponds to Y, seed to alpha_string)
uint256[2] memory hash = hashToCurve(pk, seed);
// Step 6. of IETF draft section 5.3, but see note for step 5 about +/- terms
uint256[2] memory v = linearCombination(c, gamma, cGammaWitness, s, hash, sHashWitness, zInv);
// Steps 7. and 8. of IETF draft section 5.3
uint256 derivedC = scalarFromCurvePoints(hash, pk, gamma, uWitness, v);
require(c == derivedC, "invalid proof");
}
}
// Domain-separation tag for the hash used as the final VRF output.
// Corresponds to vrfRandomOutputHashPrefix in vrf.go
uint256 internal constant VRF_RANDOM_OUTPUT_HASH_PREFIX = 3;
struct Proof {
uint256[2] pk;
uint256[2] gamma;
uint256 c;
uint256 s;
uint256 seed;
address uWitness;
uint256[2] cGammaWitness;
uint256[2] sHashWitness;
uint256 zInv;
}
/* ***************************************************************************
* @notice Returns proof's output, if proof is valid. Otherwise reverts
* @param proof vrf proof components
* @param seed seed used to generate the vrf output
*
* Throws if proof is invalid, otherwise:
* @return output i.e., the random output implied by the proof
* ***************************************************************************
*/
function randomValueFromVRFProof(Proof memory proof, uint256 seed) internal view returns (uint256 output) {
verifyVRFProof(
proof.pk,
proof.gamma,
proof.c,
proof.s,
seed,
proof.uWitness,
proof.cGammaWitness,
proof.sHashWitness,
proof.zInv
);
output = uint256(keccak256(abi.encode(VRF_RANDOM_OUTPUT_HASH_PREFIX, proof.gamma)));
}
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/interfaces/OwnableInterface.sol
pragma solidity ^0.8.0;
interface OwnableInterface {
function owner() external returns (address);
function transferOwnership(address recipient) external;
function acceptOwnership() external;
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/ConfirmedOwnerWithProposal.sol
pragma solidity ^0.8.0;
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/
contract ConfirmedOwnerWithProposal is OwnableInterface {
address private s_owner;
address private s_pendingOwner;
event OwnershipTransferRequested(address indexed from, address indexed to);
event OwnershipTransferred(address indexed from, address indexed to);
constructor(address newOwner, address pendingOwner) {
require(newOwner != address(0), "Cannot set owner to zero");
s_owner = newOwner;
if (pendingOwner != address(0)) {
_transferOwnership(pendingOwner);
}
}
/**
* @notice Allows an owner to begin transferring ownership to a new address,
* pending.
*/
function transferOwnership(address to) public override onlyOwner {
_transferOwnership(to);
}
/**
* @notice Allows an ownership transfer to be completed by the recipient.
*/
function acceptOwnership() external override {
require(msg.sender == s_pendingOwner, "Must be proposed owner");
address oldOwner = s_owner;
s_owner = msg.sender;
s_pendingOwner = address(0);
emit OwnershipTransferred(oldOwner, msg.sender);
}
/**
* @notice Get the current owner
*/
function owner() public view override returns (address) {
return s_owner;
}
/**
* @notice validate, transfer ownership, and emit relevant events
*/
function _transferOwnership(address to) private {
require(to != msg.sender, "Cannot transfer to self");
s_pendingOwner = to;
emit OwnershipTransferRequested(s_owner, to);
}
/**
* @notice validate access
*/
function _validateOwnership() internal view {
require(msg.sender == s_owner, "Only callable by owner");
}
/**
* @notice Reverts if called by anyone other than the contract owner.
*/
modifier onlyOwner() {
_validateOwnership();
_;
}
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/ConfirmedOwner.sol
pragma solidity ^0.8.0;
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/VRFConsumerBaseV2.sol
pragma solidity ^0.8.4;
/** ****************************************************************************
* @notice Interface for contracts using VRF randomness
* *****************************************************************************
* @dev PURPOSE
*
* @dev Reggie the Random Oracle (not his real job) wants to provide randomness
* @dev to Vera the verifier in such a way that Vera can be sure he's not
* @dev making his output up to suit himself. Reggie provides Vera a public key
* @dev to which he knows the secret key. Each time Vera provides a seed to
* @dev Reggie, he gives back a value which is computed completely
* @dev deterministically from the seed and the secret key.
*
* @dev Reggie provides a proof by which Vera can verify that the output was
* @dev correctly computed once Reggie tells it to her, but without that proof,
* @dev the output is indistinguishable to her from a uniform random sample
* @dev from the output space.
*
* @dev The purpose of this contract is to make it easy for unrelated contracts
* @dev to talk to Vera the verifier about the work Reggie is doing, to provide
* @dev simple access to a verifiable source of randomness. It ensures 2 things:
* @dev 1. The fulfillment came from the VRFCoordinator
* @dev 2. The consumer contract implements fulfillRandomWords.
* *****************************************************************************
* @dev USAGE
*
* @dev Calling contracts must inherit from VRFConsumerBase, and can
* @dev initialize VRFConsumerBase's attributes in their constructor as
* @dev shown:
*
* @dev contract VRFConsumer {
* @dev constructor(<other arguments>, address _vrfCoordinator, address _pli)
* @dev VRFConsumerBase(_vrfCoordinator) public {
* @dev <initialization with other arguments goes here>
* @dev }
* @dev }
*
* @dev The oracle will have given you an ID for the VRF keypair they have
* @dev committed to (let's call it keyHash). Create subscription, fund it
* @dev and your consumer contract as a consumer of it (see VRFCoordinatorInterface
* @dev subscription management functions).
* @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
* @dev callbackGasLimit, numWords),
* @dev see (VRFCoordinatorInterface for a description of the arguments).
*
* @dev Once the VRFCoordinator has received and validated the oracle's response
* @dev to your request, it will call your contract's fulfillRandomWords method.
*
* @dev The randomness argument to fulfillRandomWords is a set of random words
* @dev generated from your requestId and the blockHash of the request.
*
* @dev If your contract could have concurrent requests open, you can use the
* @dev requestId returned from requestRandomWords to track which response is associated
* @dev with which randomness request.
* @dev See "SECURITY CONSIDERATIONS" for principles to keep in mind,
* @dev if your contract could have multiple requests in flight simultaneously.
*
* @dev Colliding `requestId`s are cryptographically impossible as long as seeds
* @dev differ.
*
* *****************************************************************************
* @dev SECURITY CONSIDERATIONS
*
* @dev A method with the ability to call your fulfillRandomness method directly
* @dev could spoof a VRF response with any random value, so it's critical that
* @dev it cannot be directly called by anything other than this base contract
* @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
*
* @dev For your users to trust that your contract's random behavior is free
* @dev from malicious interference, it's best if you can write it so that all
* @dev behaviors implied by a VRF response are executed *during* your
* @dev fulfillRandomness method. If your contract must store the response (or
* @dev anything derived from it) and use it later, you must ensure that any
* @dev user-significant behavior which depends on that stored value cannot be
* @dev manipulated by a subsequent VRF request.
*
* @dev Similarly, both miners and the VRF oracle itself have some influence
* @dev over the order in which VRF responses appear on the blockchain, so if
* @dev your contract could have multiple VRF requests in flight simultaneously,
* @dev you must ensure that the order in which the VRF responses arrive cannot
* @dev be used to manipulate your contract's user-significant behavior.
*
* @dev Since the block hash of the block which contains the requestRandomness
* @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
* @dev miner could, in principle, fork the blockchain to evict the block
* @dev containing the request, forcing the request to be included in a
* @dev different block with a different hash, and therefore a different input
* @dev to the VRF. However, such an attack would incur a substantial economic
* @dev cost. This cost scales with the number of blocks the VRF oracle waits
* @dev until it calls responds to a request. It is for this reason that
* @dev that you can signal to an oracle you'd like them to wait longer before
* @dev responding to the request (however this is not enforced in the contract
* @dev and so remains effective only in the case of unmodified oracle software).
*/
abstract contract VRFConsumerBaseV2 {
error OnlyCoordinatorCanFulfill(address have, address want);
address private immutable vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) {
vrfCoordinator = _vrfCoordinator;
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBaseV2 expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) external {
if (msg.sender != vrfCoordinator) {
revert OnlyCoordinatorCanFulfill(msg.sender, vrfCoordinator);
}
fulfillRandomWords(requestId, randomWords);
}
}
// File: https://github.com/GoPlugin/contractsv2/blob/main/src/v0.8/VRFCoordinatorV2.sol
pragma solidity >0.7.6 <0.8.21;
contract VRFCoordinatorV2 is
VRF,
ConfirmedOwner,
TypeAndVersionInterface,
VRFCoordinatorV2Interface,
ERC677ReceiverInterface
{
PliTokenInterface public immutable PLI;
AggregatorV3Interface public immutable PLI_ETH_FEED;
BlockhashStoreInterface public immutable BLOCKHASH_STORE;
// We need to maintain a list of consuming addresses.
// This bound ensures we are able to loop over them as needed.
// Should a user require more consumers, they can use multiple subscriptions.
uint16 public constant MAX_CONSUMERS = 100;
error TooManyConsumers();
error InsufficientBalance();
error InvalidConsumer(uint64 subId, address consumer);
error InvalidSubscription();
error OnlyCallableFromPli();
error InvalidCalldata();
error MustBeSubOwner(address owner);
error PendingRequestExists();
error MustBeRequestedOwner(address proposedOwner);
error BalanceInvariantViolated(uint256 internalBalance, uint256 externalBalance); // Should never happen
event FundsRecovered(address to, uint256 amount);
// We use the subscription struct (1 word)
// at fulfillment time.
struct Subscription {
// There are only 1e9*1e18 = 1e27 juels in existence, so the balance can fit in uint96 (2^96 ~ 7e28)
uint96 balance; // Common pli balance used for all consumer requests.
uint64 reqCount; // For fee tiers
}
// We use the config for the mgmt APIs
struct SubscriptionConfig {
address owner; // Owner can fund/withdraw/cancel the sub.
address requestedOwner; // For safely transferring sub ownership.
// Maintains the list of keys in s_consumers.
// We do this for 2 reasons:
// 1. To be able to clean up all keys from s_consumers when canceling a subscription.
// 2. To be able to return the list of all consumers in getSubscription.
// Note that we need the s_consumers map to be able to directly check if a
// consumer is valid without reading all the consumers from storage.
address[] consumers;
}
// Note a nonce of 0 indicates an the consumer is not assigned to that subscription.
mapping(address => mapping(uint64 => uint64)) /* consumer */ /* subId */ /* nonce */
private s_consumers;
mapping(uint64 => SubscriptionConfig) /* subId */ /* subscriptionConfig */
private s_subscriptionConfigs;
mapping(uint64 => Subscription) /* subId */ /* subscription */
private s_subscriptions;
// We make the sub count public so that its possible to
// get all the current subscriptions via getSubscription.
uint64 private s_currentSubId;
// s_totalBalance tracks the total pli sent to/from
// this contract through onTokenTransfer, cancelSubscription and oracleWithdraw.
// A discrepancy with this contract's pli balance indicates someone
// sent tokens using transfer and so we may need to use recoverFunds.
uint96 private s_totalBalance;
event SubscriptionCreated(uint64 indexed subId, address owner);
event SubscriptionFunded(uint64 indexed subId, uint256 oldBalance, uint256 newBalance);
event SubscriptionConsumerAdded(uint64 indexed subId, address consumer);
event SubscriptionConsumerRemoved(uint64 indexed subId, address consumer);
event SubscriptionCanceled(uint64 indexed subId, address to, uint256 amount);
event SubscriptionOwnerTransferRequested(uint64 indexed subId, address from, address to);
event SubscriptionOwnerTransferred(uint64 indexed subId, address from, address to);
// Set this maximum to 200 to give us a 56 block window to fulfill
// the request before requiring the block hash feeder.
uint16 public constant MAX_REQUEST_CONFIRMATIONS = 200;
uint32 public constant MAX_NUM_WORDS = 500;
// 5k is plenty for an EXTCODESIZE call (2600) + warm CALL (100)
// and some arithmetic operations.
uint256 private constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
error InvalidRequestConfirmations(uint16 have, uint16 min, uint16 max);
error GasLimitTooBig(uint32 have, uint32 want);
error NumWordsTooBig(uint32 have, uint32 want);
error ProvingKeyAlreadyRegistered(bytes32 keyHash);
error NoSuchProvingKey(bytes32 keyHash);
error InvalidPliWeiPrice(int256 pliWei);
error InsufficientGasForConsumer(uint256 have, uint256 want);
error NoCorrespondingRequest();
error IncorrectCommitment();
error BlockhashNotInStore(uint256 blockNum);
error PaymentTooLarge();
error Reentrant();
struct RequestCommitment {
uint64 blockNum;
uint64 subId;
uint32 callbackGasLimit;
uint32 numWords;
address sender;
}
mapping(bytes32 => address) /* keyHash */ /* oracle */
private s_provingKeys;
bytes32[] private s_provingKeyHashes;
mapping(address => uint96) /* oracle */ /* PLI balance */
private s_withdrawableTokens;
mapping(uint256 => bytes32) /* requestID */ /* commitment */
private s_requestCommitments;
event ProvingKeyRegistered(bytes32 keyHash, address indexed oracle);
event ProvingKeyDeregistered(bytes32 keyHash, address indexed oracle);
event RandomWordsRequested(
bytes32 indexed keyHash,
uint256 requestId,
uint256 preSeed,
uint64 indexed subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords,
address indexed sender
);
event RandomWordsFulfilled(uint256 indexed requestId, uint256 outputSeed, uint96 payment, bool success);
struct Config {
uint16 minimumRequestConfirmations;
uint32 maxGasLimit;
// Reentrancy protection.
bool reentrancyLock;
// stalenessSeconds is how long before we consider the feed price to be stale
// and fallback to fallbackWeiPerUnitPli.
uint32 stalenessSeconds;
// Gas to cover oracle payment after we calculate the payment.
// We make it configurable in case those operations are repriced.
uint32 gasAfterPaymentCalculation;
}
int256 private s_fallbackWeiPerUnitPli;
Config private s_config;
FeeConfig private s_feeConfig;
struct FeeConfig {
// Flat fee charged per fulfillment in millionths of pli
// So fee range is [0, 2^32/10^6].
uint32 fulfillmentFlatFeePliPPMTier1;
uint32 fulfillmentFlatFeePliPPMTier2;
uint32 fulfillmentFlatFeePliPPMTier3;
uint32 fulfillmentFlatFeePliPPMTier4;
uint32 fulfillmentFlatFeePliPPMTier5;
uint24 reqsForTier2;
uint24 reqsForTier3;
uint24 reqsForTier4;
uint24 reqsForTier5;
}
event ConfigSet(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitPli,
FeeConfig feeConfig
);
constructor(
address pli,
address blockhashStore,
address pliEthFeed
) ConfirmedOwner(msg.sender) {
PLI = PliTokenInterface(pli);
PLI_ETH_FEED = AggregatorV3Interface(pliEthFeed);
BLOCKHASH_STORE = BlockhashStoreInterface(blockhashStore);
}
/**
* @notice Registers a proving key to an oracle.
* @param oracle address of the oracle
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner {
bytes32 kh = hashOfKey(publicProvingKey);
if (s_provingKeys[kh] != address(0)) {
revert ProvingKeyAlreadyRegistered(kh);
}
s_provingKeys[kh] = oracle;
s_provingKeyHashes.push(kh);
emit ProvingKeyRegistered(kh, oracle);
}
/**
* @notice Deregisters a proving key to an oracle.
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function deregisterProvingKey(uint256[2] calldata publicProvingKey) external onlyOwner {
bytes32 kh = hashOfKey(publicProvingKey);
address oracle = s_provingKeys[kh];
if (oracle == address(0)) {
revert NoSuchProvingKey(kh);
}
delete s_provingKeys[kh];
for (uint256 i = 0; i < s_provingKeyHashes.length; i++) {
if (s_provingKeyHashes[i] == kh) {
bytes32 last = s_provingKeyHashes[s_provingKeyHashes.length - 1];
// Copy last element and overwrite kh to be deleted with it
s_provingKeyHashes[i] = last;
s_provingKeyHashes.pop();
}
}
emit ProvingKeyDeregistered(kh, oracle);
}
/**
* @notice Returns the proving key hash key associated with this public key
* @param publicKey the key to return the hash of
*/
function hashOfKey(uint256[2] memory publicKey) public pure returns (bytes32) {
return keccak256(abi.encode(publicKey));
}
/**
* @notice Sets the configuration of the vrfv2 coordinator
* @param minimumRequestConfirmations global min for request confirmations
* @param maxGasLimit global max for request gas limit
* @param stalenessSeconds if the eth/pli feed is more stale then this, use the fallback price
* @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
* @param fallbackWeiPerUnitPli fallback eth/pli price in the case of a stale feed
* @param feeConfig fee tier configuration
*/
function setConfig(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitPli,
FeeConfig memory feeConfig
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
minimumRequestConfirmations,
minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
if (fallbackWeiPerUnitPli <= 0) {
revert InvalidPliWeiPrice(fallbackWeiPerUnitPli);
}
s_config = Config({
minimumRequestConfirmations: minimumRequestConfirmations,
maxGasLimit: maxGasLimit,
stalenessSeconds: stalenessSeconds,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
reentrancyLock: false
});
s_feeConfig = feeConfig;
s_fallbackWeiPerUnitPli = fallbackWeiPerUnitPli;
emit ConfigSet(
minimumRequestConfirmations,
maxGasLimit,
stalenessSeconds,
gasAfterPaymentCalculation,
fallbackWeiPerUnitPli,
s_feeConfig
);
}
function getConfig()
external
view
returns (
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation
)
{
return (
s_config.minimumRequestConfirmations,
s_config.maxGasLimit,
s_config.stalenessSeconds,
s_config.gasAfterPaymentCalculation
);
}
function getFeeConfig()
external
view
returns (
uint32 fulfillmentFlatFeePliPPMTier1,
uint32 fulfillmentFlatFeePliPPMTier2,
uint32 fulfillmentFlatFeePliPPMTier3,
uint32 fulfillmentFlatFeePliPPMTier4,
uint32 fulfillmentFlatFeePliPPMTier5,
uint24 reqsForTier2,
uint24 reqsForTier3,
uint24 reqsForTier4,
uint24 reqsForTier5
)
{
return (
s_feeConfig.fulfillmentFlatFeePliPPMTier1,
s_feeConfig.fulfillmentFlatFeePliPPMTier2,
s_feeConfig.fulfillmentFlatFeePliPPMTier3,
s_feeConfig.fulfillmentFlatFeePliPPMTier4,
s_feeConfig.fulfillmentFlatFeePliPPMTier5,
s_feeConfig.reqsForTier2,
s_feeConfig.reqsForTier3,
s_feeConfig.reqsForTier4,
s_feeConfig.reqsForTier5
);
}
function getTotalBalance() external view returns (uint256) {
return s_totalBalance;
}
function getFallbackWeiPerUnitPli() external view returns (int256) {
return s_fallbackWeiPerUnitPli;
}
/**
* @notice Owner cancel subscription, sends remaining pli directly to the subscription owner.
* @param subId subscription id
* @dev notably can be called even if there are pending requests, outstanding ones may fail onchain
*/
function ownerCancelSubscription(uint64 subId) external onlyOwner {
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
cancelSubscriptionHelper(subId, s_subscriptionConfigs[subId].owner);
}
/**
* @notice Recover pli sent with transfer instead of transferAndCall.
* @param to address to send pli to
*/
function recoverFunds(address to) external onlyOwner {
uint256 externalBalance = PLI.balanceOf(address(this));
uint256 internalBalance = uint256(s_totalBalance);
if (internalBalance > externalBalance) {
revert BalanceInvariantViolated(internalBalance, externalBalance);
}
if (internalBalance < externalBalance) {
uint256 amount = externalBalance - internalBalance;
PLI.transfer(to, amount);
emit FundsRecovered(to, amount);
}
// If the balances are equal, nothing to be done.
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function getRequestConfig()
external
view
override
returns (
uint16,
uint32,
bytes32[] memory
)
{
return (s_config.minimumRequestConfirmations, s_config.maxGasLimit, s_provingKeyHashes);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 requestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external override nonReentrant returns (uint256) {
// Input validation using the subscription storage.
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// Its important to ensure that the consumer is in fact who they say they
// are, otherwise they could use someone else's subscription balance.
// A nonce of 0 indicates consumer is not allocated to the sub.
uint64 currentNonce = s_consumers[msg.sender][subId];
if (currentNonce == 0) {
revert InvalidConsumer(subId, msg.sender);
}
// Input validation using the config storage word.
if (
requestConfirmations < s_config.minimumRequestConfirmations || requestConfirmations > MAX_REQUEST_CONFIRMATIONS
) {
revert InvalidRequestConfirmations(
requestConfirmations,
s_config.minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
// No lower bound on the requested gas limit. A user could request 0
// and they would simply be billed for the proof verification and wouldn't be
// able to do anything with the random value.
if (callbackGasLimit > s_config.maxGasLimit) {
revert GasLimitTooBig(callbackGasLimit, s_config.maxGasLimit);
}
if (numWords > MAX_NUM_WORDS) {
revert NumWordsTooBig(numWords, MAX_NUM_WORDS);
}
// Note we do not check whether the keyHash is valid to save gas.
// The consequence for users is that they can send requests
// for invalid keyHashes which will simply not be fulfilled.
uint64 nonce = currentNonce + 1;
(uint256 requestId, uint256 preSeed) = computeRequestId(keyHash, msg.sender, subId, nonce);
s_requestCommitments[requestId] = keccak256(
abi.encode(requestId, block.number, subId, callbackGasLimit, numWords, msg.sender)
);
emit RandomWordsRequested(
keyHash,
requestId,
preSeed,
subId,
requestConfirmations,
callbackGasLimit,
numWords,
msg.sender
);
s_consumers[msg.sender][subId] = nonce;
return requestId;
}
/**
* @notice Get request commitment
* @param requestId id of request
* @dev used to determine if a request is fulfilled or not
*/
function getCommitment(uint256 requestId) external view returns (bytes32) {
return s_requestCommitments[requestId];
}
function computeRequestId(
bytes32 keyHash,
address sender,
uint64 subId,
uint64 nonce
) private pure returns (uint256, uint256) {
uint256 preSeed = uint256(keccak256(abi.encode(keyHash, sender, subId, nonce)));
return (uint256(keccak256(abi.encode(keyHash, preSeed))), preSeed);
}
/**
* @dev calls target address with exactly gasAmount gas and data as calldata
* or reverts if at least gasAmount gas is not available.
*/
function callWithExactGas(
uint256 gasAmount,
address target,
bytes memory data
) private returns (bool success) {
// solhint-disable-next-line no-inline-assembly
assembly {
let g := gas()
// Compute g -= GAS_FOR_CALL_EXACT_CHECK and check for underflow
// The gas actually passed to the callee is min(gasAmount, 63//64*gas available).
// We want to ensure that we revert if gasAmount > 63//64*gas available
// as we do not want to provide them with less, however that check itself costs
// gas. GAS_FOR_CALL_EXACT_CHECK ensures we have at least enough gas to be able
// to revert if gasAmount > 63//64*gas available.
if lt(g, GAS_FOR_CALL_EXACT_CHECK) {
revert(0, 0)
}
g := sub(g, GAS_FOR_CALL_EXACT_CHECK)
// if g - g//64 <= gasAmount, revert
// (we subtract g//64 because of EIP-150)
if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
revert(0, 0)
}
// solidity calls check that a contract actually exists at the destination, so we do the same
if iszero(extcodesize(target)) {
revert(0, 0)
}
// call and return whether we succeeded. ignore return data
// call(gas,addr,value,argsOffset,argsLength,retOffset,retLength)
success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
}
return success;
}
function getRandomnessFromProof(Proof memory proof, RequestCommitment memory rc)
private
view
returns (
bytes32 keyHash,
uint256 requestId,
uint256 randomness
)
{
keyHash = hashOfKey(proof.pk);
// Only registered proving keys are permitted.
address oracle = s_provingKeys[keyHash];
if (oracle == address(0)) {
revert NoSuchProvingKey(keyHash);
}
requestId = uint256(keccak256(abi.encode(keyHash, proof.seed)));
bytes32 commitment = s_requestCommitments[requestId];
if (commitment == 0) {
revert NoCorrespondingRequest();
}
if (
commitment != keccak256(abi.encode(requestId, rc.blockNum, rc.subId, rc.callbackGasLimit, rc.numWords, rc.sender))
) {
revert IncorrectCommitment();
}
bytes32 blockHash = blockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
blockHash = BLOCKHASH_STORE.getBlockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
revert BlockhashNotInStore(rc.blockNum);
}
}
// The seed actually used by the VRF machinery, mixing in the blockhash
uint256 actualSeed = uint256(keccak256(abi.encodePacked(proof.seed, blockHash)));
randomness = VRF.randomValueFromVRFProof(proof, actualSeed); // Reverts on failure
}
/*
* @notice Compute fee based on the request count
* @param reqCount number of requests
* @return feePPM fee in PLI PPM
*/
function getFeeTier(uint64 reqCount) public view returns (uint32) {
FeeConfig memory fc = s_feeConfig;
if (0 <= reqCount && reqCount <= fc.reqsForTier2) {
return fc.fulfillmentFlatFeePliPPMTier1;
}
if (fc.reqsForTier2 < reqCount && reqCount <= fc.reqsForTier3) {
return fc.fulfillmentFlatFeePliPPMTier2;
}
if (fc.reqsForTier3 < reqCount && reqCount <= fc.reqsForTier4) {
return fc.fulfillmentFlatFeePliPPMTier3;
}
if (fc.reqsForTier4 < reqCount && reqCount <= fc.reqsForTier5) {
return fc.fulfillmentFlatFeePliPPMTier4;
}
return fc.fulfillmentFlatFeePliPPMTier5;
}
/*
* @notice Fulfill a randomness request
* @param proof contains the proof and randomness
* @param rc request commitment pre-image, committed to at request time
* @return payment amount billed to the subscription
* @dev simulated offchain to determine if sufficient balance is present to fulfill the request
*/
function fulfillRandomWords(Proof memory proof, RequestCommitment memory rc) external nonReentrant returns (uint96) {
uint256 startGas = gasleft();
(bytes32 keyHash, uint256 requestId, uint256 randomness) = getRandomnessFromProof(proof, rc);
uint256[] memory randomWords = new uint256[](rc.numWords);
for (uint256 i = 0; i < rc.numWords; i++) {
randomWords[i] = uint256(keccak256(abi.encode(randomness, i)));
}
delete s_requestCommitments[requestId];
VRFConsumerBaseV2 v;
bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, requestId, randomWords);
// Call with explicitly the amount of callback gas requested
// Important to not let them exhaust the gas budget and avoid oracle payment.
// Do not allow any non-view/non-pure coordinator functions to be called
// during the consumers callback code via reentrancyLock.
// Note that callWithExactGas will revert if we do not have sufficient gas
// to give the callee their requested amount.
s_config.reentrancyLock = true;
bool success = callWithExactGas(rc.callbackGasLimit, rc.sender, resp);
s_config.reentrancyLock = false;
// Increment the req count for fee tier selection.
uint64 reqCount = s_subscriptions[rc.subId].reqCount;
s_subscriptions[rc.subId].reqCount += 1;
// We want to charge users exactly for how much gas they use in their callback.
// The gasAfterPaymentCalculation is meant to cover these additional operations where we
// decrement the subscription balance and increment the oracles withdrawable balance.
// We also add the flat pli fee to the payment amount.
// Its specified in millionths of pli, if s_config.fulfillmentFlatFeePliPPM = 1
// 1 pli / 1e6 = 1e18 juels / 1e6 = 1e12 juels.
uint96 payment = calculatePaymentAmount(
startGas,
s_config.gasAfterPaymentCalculation,
getFeeTier(reqCount),
tx.gasprice
);
if (s_subscriptions[rc.subId].balance < payment) {
revert InsufficientBalance();
}
s_subscriptions[rc.subId].balance -= payment;
s_withdrawableTokens[s_provingKeys[keyHash]] += payment;
// Include payment in the event for tracking costs.
emit RandomWordsFulfilled(requestId, randomness, payment, success);
return payment;
}
// Get the amount of gas used for fulfillment
function calculatePaymentAmount(
uint256 startGas,
uint256 gasAfterPaymentCalculation,
uint32 fulfillmentFlatFeePliPPM,
uint256 weiPerUnitGas
) internal view returns (uint96) {
int256 weiPerUnitPli;
weiPerUnitPli = getFeedData();
if (weiPerUnitPli <= 0) {
revert InvalidPliWeiPrice(weiPerUnitPli);
}
// (1e18 juels/pli) (wei/gas * gas) / (wei/pli) = juels
uint256 paymentNoFee = (1e18 * weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft())) /
uint256(weiPerUnitPli);
uint256 fee = 1e12 * uint256(fulfillmentFlatFeePliPPM);
if (paymentNoFee > (1e27 - fee)) {
revert PaymentTooLarge(); // Payment + fee cannot be more than all of the pli in existence.
}
return uint96(paymentNoFee + fee);
}
function getFeedData() private view returns (int256) {
uint32 stalenessSeconds = s_config.stalenessSeconds;
bool staleFallback = stalenessSeconds > 0;
uint256 timestamp;
int256 weiPerUnitPli;
(, weiPerUnitPli, , timestamp, ) = PLI_ETH_FEED.latestRoundData();
// solhint-disable-next-line not-rely-on-time
if (staleFallback && stalenessSeconds < block.timestamp - timestamp) {
weiPerUnitPli = s_fallbackWeiPerUnitPli;
}
return weiPerUnitPli;
}
/*
* @notice Oracle withdraw PLI earned through fulfilling requests
* @param recipient where to send the funds
* @param amount amount to withdraw
*/
function oracleWithdraw(address recipient, uint96 amount) external nonReentrant {
if (s_withdrawableTokens[msg.sender] < amount) {
revert InsufficientBalance();
}
s_withdrawableTokens[msg.sender] -= amount;
s_totalBalance -= amount;
if (!PLI.transfer(recipient, amount)) {
revert InsufficientBalance();
}
}
function onTokenTransfer(
address, /* sender */
uint256 amount,
bytes calldata data
) external override nonReentrant {
if (msg.sender != address(PLI)) {
revert OnlyCallableFromPli();
}
if (data.length != 32) {
revert InvalidCalldata();
}
uint64 subId = abi.decode(data, (uint64));
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// We do not check that the msg.sender is the subscription owner,
// anyone can fund a subscription.
uint256 oldBalance = s_subscriptions[subId].balance;
s_subscriptions[subId].balance += uint96(amount);
s_totalBalance += uint96(amount);
emit SubscriptionFunded(subId, oldBalance, oldBalance + amount);
}
function getCurrentSubId() external view returns (uint64) {
return s_currentSubId;
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function getSubscription(uint64 subId)
external
view
override
returns (
uint96 balance,
uint64 reqCount,
address owner,
address[] memory consumers
)
{
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
return (
s_subscriptions[subId].balance,
s_subscriptions[subId].reqCount,
s_subscriptionConfigs[subId].owner,
s_subscriptionConfigs[subId].consumers
);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function createSubscription() external override nonReentrant returns (uint64) {
s_currentSubId++;
uint64 currentSubId = s_currentSubId;
address[] memory consumers = new address[](0);
s_subscriptions[currentSubId] = Subscription({balance: 0, reqCount: 0});
s_subscriptionConfigs[currentSubId] = SubscriptionConfig({
owner: msg.sender,
requestedOwner: address(0),
consumers: consumers
});
emit SubscriptionCreated(currentSubId, msg.sender);
return currentSubId;
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function requestSubscriptionOwnerTransfer(uint64 subId, address newOwner)
external
override
onlySubOwner(subId)
nonReentrant
{
// Proposing to address(0) would never be claimable so don't need to check.
if (s_subscriptionConfigs[subId].requestedOwner != newOwner) {
s_subscriptionConfigs[subId].requestedOwner = newOwner;
emit SubscriptionOwnerTransferRequested(subId, msg.sender, newOwner);
}
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external override nonReentrant {
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
if (s_subscriptionConfigs[subId].requestedOwner != msg.sender) {
revert MustBeRequestedOwner(s_subscriptionConfigs[subId].requestedOwner);
}
address oldOwner = s_subscriptionConfigs[subId].owner;
s_subscriptionConfigs[subId].owner = msg.sender;
s_subscriptionConfigs[subId].requestedOwner = address(0);
emit SubscriptionOwnerTransferred(subId, oldOwner, msg.sender);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function removeConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
if (s_consumers[consumer][subId] == 0) {
revert InvalidConsumer(subId, consumer);
}
// Note bounded by MAX_CONSUMERS
address[] memory consumers = s_subscriptionConfigs[subId].consumers;
uint256 lastConsumerIndex = consumers.length - 1;
for (uint256 i = 0; i < consumers.length; i++) {
if (consumers[i] == consumer) {
address last = consumers[lastConsumerIndex];
// Storage write to preserve last element
s_subscriptionConfigs[subId].consumers[i] = last;
// Storage remove last element
s_subscriptionConfigs[subId].consumers.pop();
break;
}
}
delete s_consumers[consumer][subId];
emit SubscriptionConsumerRemoved(subId, consumer);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function addConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
// Already maxed, cannot add any more consumers.
if (s_subscriptionConfigs[subId].consumers.length == MAX_CONSUMERS) {
revert TooManyConsumers();
}
if (s_consumers[consumer][subId] != 0) {
// Idempotence - do nothing if already added.
// Ensures uniqueness in s_subscriptions[subId].consumers.
return;
}
// Initialize the nonce to 1, indicating the consumer is allocated.
s_consumers[consumer][subId] = 1;
s_subscriptionConfigs[subId].consumers.push(consumer);
emit SubscriptionConsumerAdded(subId, consumer);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function cancelSubscription(uint64 subId, address to) external override onlySubOwner(subId) nonReentrant {
if (pendingRequestExists(subId)) {
revert PendingRequestExists();
}
cancelSubscriptionHelper(subId, to);
}
function cancelSubscriptionHelper(uint64 subId, address to) private nonReentrant {
SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
Subscription memory sub = s_subscriptions[subId];
uint96 balance = sub.balance;
// Note bounded by MAX_CONSUMERS;
// If no consumers, does nothing.
for (uint256 i = 0; i < subConfig.consumers.length; i++) {
delete s_consumers[subConfig.consumers[i]][subId];
}
delete s_subscriptionConfigs[subId];
delete s_subscriptions[subId];
s_totalBalance -= balance;
if (!PLI.transfer(to, uint256(balance))) {
revert InsufficientBalance();
}
emit SubscriptionCanceled(subId, to, balance);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
* @dev Looping is bounded to MAX_CONSUMERS*(number of keyhashes).
* @dev Used to disable subscription canceling while outstanding request are present.
*/
function pendingRequestExists(uint64 subId) public view override returns (bool) {
SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
for (uint256 i = 0; i < subConfig.consumers.length; i++) {
for (uint256 j = 0; j < s_provingKeyHashes.length; j++) {
(uint256 reqId, ) = computeRequestId(
s_provingKeyHashes[j],
subConfig.consumers[i],
subId,
s_consumers[subConfig.consumers[i]][subId]
);
if (s_requestCommitments[reqId] != 0) {
return true;
}
}
}
return false;
}
modifier onlySubOwner(uint64 subId) {
address owner = s_subscriptionConfigs[subId].owner;
if (owner == address(0)) {
revert InvalidSubscription();
}
if (msg.sender != owner) {
revert MustBeSubOwner(owner);
}
_;
}
modifier nonReentrant() {
if (s_config.reentrancyLock) {
revert Reentrant();
}
_;
}
/**
* @notice The type and version of this contract
* @return Type and version string
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "VRFCoordinatorV2 1.0.0";
}
}
// File: contracts/flipcoin.sol
pragma solidity ^0.8.20;
/**
* @title IERC20
* @dev ERC20 interface for token interactions
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function mint(address account, uint256 amount) external;
function burn(uint256 amount) external;
function burnFrom(address account, uint256 amount) external;
function getRemainingMintable() external view returns (uint256);
}
/**
* @title Game State Structure
* @dev Tracks current game status with storage-optimized data types
*/
struct GameState {
bool isActive;
bool completed;
uint8 chosenSide;
uint8 result;
uint256 amount;
uint256 payout;
}
/**
* @title Bet History Structure
* @dev Records individual bet data with optimized storage
*/
struct BetHistory {
uint8 chosenSide;
uint8 flippedResult;
uint32 timestamp;
uint256 amount;
uint256 payout;
}
/**
* @title User Data Structure
* @dev Maintains game state and bet history for each player
*/
struct UserData {
GameState currentGame;
uint256 currentRequestId;
BetHistory[] recentBets;
uint32 lastPlayedTimestamp;
uint256 lastPlayedBlock;
uint8 historyIndex;
bool requestFulfilled;
}
/**
* @title Flip
* @dev Provably fair flip game using VRF for randomness
*/
contract Flip is ReentrancyGuard, Pausable, VRFConsumerBaseV2, Ownable {
// ============ Events ============
event BetPlaced(address indexed player, uint256 requestId, uint8 chosenSide, uint256 amount);
event GameCompleted(address indexed player, uint256 requestId, uint8 result, uint256 payout);
event GameRecovered(address indexed player, uint256 requestId, uint256 refundAmount);
// ============ Custom Errors ============
error InvalidBetParameters(string reason);
error InsufficientUserBalance(uint256 required, uint256 available);
error TransferFailed(address from, address to, uint256 amount);
error BurnFailed(address account, uint256 amount);
error MintFailed(address account, uint256 amount);
error PayoutCalculationError(string message);
error InsufficientAllowance(uint256 required, uint256 allowed);
error GameError(string reason);
error VRFError(string reason);
error MaxPayoutExceeded(uint256 potentialPayout, uint256 maxAllowed);
// ============ Constants ============
uint8 public constant HEADS = 1;
uint8 public constant TAILS = 2;
uint8 private constant MAX_SIDES = 2;
uint8 public constant MAX_HISTORY_SIZE = 10;
uint256 public constant MAX_BET_AMOUNT = 10_000_000 * 10**18;
uint256 public constant MAX_POSSIBLE_PAYOUT = 20_000_000 * 10**18; // 10M * 2
uint32 private constant GAME_TIMEOUT = 1 hours;
uint256 private constant BLOCK_THRESHOLD = 300;
// Special result values
uint8 public constant RESULT_FORCE_STOPPED = 254;
uint8 public constant RESULT_RECOVERED = 255;
// ============ State Variables ============
IERC20 public immutable gamaToken;
mapping(address => UserData) private userData;
// Game Statistics
uint256 public totalGamesPlayed;
uint256 public totalPayoutAmount;
uint256 public totalWageredAmount;
// VRF Variables
VRFCoordinatorV2Interface private immutable COORDINATOR;
uint64 private immutable s_subscriptionId;
bytes32 private immutable s_keyHash;
uint32 private immutable callbackGasLimit;
uint16 private immutable requestConfirmations;
uint8 private immutable numWords;
// Request tracking
struct RequestStatus {
bool fulfilled;
bool exists;
uint256[] randomWords;
}
mapping(uint256 => RequestStatus) public s_requests;
mapping(uint256 => address) private requestToPlayer;
mapping(uint256 => bool) private activeRequestIds;
// ============ Constructor ============
/**
* @notice Contract constructor
* @param _gamaTokenAddress Address of the token contract
* @param vrfCoordinator Address of the VRF coordinator
* @param subscriptionId VRF subscription ID
* @param keyHash VRF key hash for the network
* @param _callbackGasLimit Gas limit for VRF callback
* @param _requestConfirmations Number of confirmations for VRF request
* @param _numWords Number of random words to request
*/
constructor(
address _gamaTokenAddress,
address vrfCoordinator,
uint64 subscriptionId,
bytes32 keyHash,
uint32 _callbackGasLimit,
uint16 _requestConfirmations,
uint8 _numWords
) VRFConsumerBaseV2(vrfCoordinator) Ownable(msg.sender) {
require(_gamaTokenAddress != address(0), "Token address cannot be zero");
require(vrfCoordinator != address(0), "VRF coordinator cannot be zero");
require(_callbackGasLimit > 0, "Callback gas limit cannot be zero");
require(_numWords > 0, "Number of words cannot be zero");
gamaToken = IERC20(_gamaTokenAddress);
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
s_subscriptionId = subscriptionId;
s_keyHash = keyHash;
callbackGasLimit = _callbackGasLimit;
requestConfirmations = _requestConfirmations;
numWords = _numWords;
}
// ============ External Functions ============
/**
* @notice Place a bet on a flip (heads or tails)
* @param chosenSide Side to bet on (1=HEADS, 2=TAILS)
* @param amount Token amount to bet
* @return requestId VRF request ID
*/
function flipCoin(uint8 chosenSide, uint256 amount) external nonReentrant whenNotPaused returns (uint256 requestId) {
// ===== CHECKS =====
// 1. Basic input validation
if (amount == 0) revert InvalidBetParameters("Bet amount cannot be zero");
if (amount > MAX_BET_AMOUNT) revert InvalidBetParameters("Bet amount too large");
if (chosenSide != HEADS && chosenSide != TAILS) revert InvalidBetParameters("Invalid chosen side (must be 1 for HEADS or 2 for TAILS)");
// 2. Check if user has an active game
UserData storage user = userData[msg.sender];
if (user.currentGame.isActive) revert GameError("User has an active game");
if (user.currentRequestId != 0) revert GameError("User has a pending request");
// 3. Balance, allowance
_checkBalancesAndAllowances(msg.sender, amount);
// Calculate potential payout
uint256 potentialPayout = amount * 2;
if (potentialPayout / 2 != amount) revert PayoutCalculationError("Payout calculation overflow");
if (potentialPayout > MAX_POSSIBLE_PAYOUT) {
revert MaxPayoutExceeded(potentialPayout, MAX_POSSIBLE_PAYOUT);
}
// 4. Check if potential payout doesn't exceed remaining mintable amount
uint256 remainingMintable = gamaToken.getRemainingMintable();
if (potentialPayout > remainingMintable) {
revert MaxPayoutExceeded(potentialPayout, remainingMintable);
}
// ===== EFFECTS =====
// 5. Burn tokens first
gamaToken.burnFrom(msg.sender, amount);
// Update total wagered amount
totalWageredAmount += amount;
// 6. Request random number using VRF
requestId = COORDINATOR.requestRandomWords(
s_keyHash,
s_subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
// 7. Record the request
s_requests[requestId] = RequestStatus({
randomWords: new uint256[](0),
exists: true,
fulfilled: false
});
// 8. Store request mapping
requestToPlayer[requestId] = msg.sender;
activeRequestIds[requestId] = true;
// Update timestamp and block number
user.lastPlayedTimestamp = uint32(block.timestamp);
user.lastPlayedBlock = block.number;
user.requestFulfilled = false;
// 9. Update user's game state
user.currentGame = GameState({
isActive: true,
completed: false,
chosenSide: chosenSide,
result: 0,
amount: amount,
payout: 0
});
user.currentRequestId = requestId;
emit BetPlaced(msg.sender, requestId, chosenSide, amount);
return requestId;
}
/**
* @notice VRF Coordinator callback function
* @param requestId VRF request identifier
* @param randomWords Random results from VRF
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override nonReentrant {
// ===== CHECKS =====
// 1. Validate VRF request
RequestStatus storage request = s_requests[requestId];
if (!request.exists) revert VRFError("Request not found");
if (request.fulfilled) revert VRFError("Request already fulfilled");
if (randomWords.length != numWords) revert VRFError("Invalid random words length");
// 2. Validate player and game state
address player = requestToPlayer[requestId];
if (player == address(0)) revert VRFError("Invalid player address");
UserData storage user = userData[player];
if (user.currentRequestId != requestId) revert GameError("Request ID mismatch");
// Mark request as fulfilled to prevent race conditions
request.fulfilled = true;
request.randomWords = randomWords;
user.requestFulfilled = true;
// Check if game is still active
if (!user.currentGame.isActive) {
// Game already recovered or force-stopped, clean up ALL request data
delete s_requests[requestId];
delete requestToPlayer[requestId];
delete activeRequestIds[requestId];
user.currentRequestId = 0;
user.requestFulfilled = false;
return;
}
// Cache important values
uint8 chosenSide = user.currentGame.chosenSide;
uint256 betAmount = user.currentGame.amount;
// ===== EFFECTS =====
// 1. Calculate result (HEADS=1, TAILS=2)
uint8 result = uint8((randomWords[0] % MAX_SIDES) + 1);
// 2. Calculate payout
uint256 payout = 0;
if (chosenSide == result) {
// Ensure safe multiplication
if (betAmount > type(uint256).max / 2) {
revert PayoutCalculationError("Bet amount too large for payout calculation");
}
payout = betAmount * 2;
}
// 4. Update game state
user.currentGame.result = result;
user.currentGame.isActive = false;
user.currentGame.completed = true;
user.currentGame.payout = payout;
// 5. Update game history
_updateUserHistory(
user,
chosenSide,
result,
betAmount,
payout
);
// Update total payout if player won
if (payout > 0) {
totalPayoutAmount += payout;
}
// Update total games played counter
unchecked { ++totalGamesPlayed; }
// Cleanup
delete requestToPlayer[requestId];
delete activeRequestIds[requestId];
delete s_requests[requestId];
user.currentRequestId = 0;
user.requestFulfilled = false;
// ===== INTERACTIONS =====
// Process payout if player won
if (payout > 0) {
gamaToken.mint(player, payout);
}
emit GameCompleted(player, requestId, result, payout);
}
/**
* @notice Recover from a stuck game and receive refund
*/
function recoverOwnStuckGame() external nonReentrant whenNotPaused {
UserData storage user = userData[msg.sender];
// ===== CHECKS =====
// Check if user has an active bet
if (!user.currentGame.isActive) revert GameError("No active game");
uint256 requestId = user.currentRequestId;
// Ensure there is a request to recover from
if (requestId == 0) {
revert GameError("No pending request to recover");
}
// Check for race condition with VRF callback first
if (s_requests[requestId].fulfilled &&
(block.number <= user.lastPlayedBlock + 10)) {
revert GameError("Request just fulfilled, let VRF complete");
}
// Check if game is stale - with modified conditions
bool hasBlockThresholdPassed = block.number > user.lastPlayedBlock + BLOCK_THRESHOLD;
bool hasTimeoutPassed = block.timestamp > user.lastPlayedTimestamp + GAME_TIMEOUT;
// Modified: Only require that the request exists, not that it's processed
bool hasVrfRequest = requestId != 0 && s_requests[requestId].exists;
// Check eligibility with modified conditions
if (!hasBlockThresholdPassed || !hasTimeoutPassed || !hasVrfRequest) {
revert GameError("Game not eligible for recovery yet");
}
// ===== EFFECTS =====
// Calculate amount to refund
uint256 refundAmount = user.currentGame.amount;
if (refundAmount == 0) revert GameError("Nothing to refund");
// Clean up request data
delete s_requests[requestId];
delete requestToPlayer[requestId];
delete activeRequestIds[requestId];
// Update game state
user.currentGame.completed = true;
user.currentGame.isActive = false;
user.currentGame.result = RESULT_RECOVERED;
user.currentGame.payout = refundAmount;
user.currentRequestId = 0;
user.requestFulfilled = false;
// ===== INTERACTIONS =====
// Refund player
gamaToken.mint(msg.sender, refundAmount);
// Add to bet history
_updateUserHistory(
user,
user.currentGame.chosenSide,
RESULT_RECOVERED,
refundAmount,
refundAmount
);
emit GameRecovered(msg.sender, requestId, refundAmount);
}
/**
* @notice Force stop a game and refund the player
* @param player Player address
*/
function forceStopGame(address player) external onlyOwner nonReentrant {
UserData storage user = userData[player];
// ===== CHECKS =====
if (!user.currentGame.isActive) revert GameError("No active game");
uint256 requestId = user.currentRequestId;
// Check for race condition with VRF callback first
if (requestId != 0 && s_requests[requestId].fulfilled &&
(block.number <= user.lastPlayedBlock + 10)) {
revert GameError("Request just fulfilled, let VRF complete");
}
// Check if game is stale - with modified conditions
bool hasBlockThresholdPassed = block.number > user.lastPlayedBlock + BLOCK_THRESHOLD;
bool hasTimeoutPassed = block.timestamp > user.lastPlayedTimestamp + GAME_TIMEOUT;
// Modified: Only require that the request exists, not that it's processed
bool hasVrfRequest = requestId != 0 && s_requests[requestId].exists;
// Check eligibility with modified conditions
if (!hasBlockThresholdPassed || !hasTimeoutPassed || !hasVrfRequest) {
revert GameError("Game not eligible for force stop yet");
}
uint256 refundAmount = user.currentGame.amount;
if (refundAmount == 0) revert GameError("Nothing to refund");
// ===== EFFECTS =====
// Clean up request data
if (requestId != 0) {
delete requestToPlayer[requestId];
delete activeRequestIds[requestId];
delete s_requests[requestId];
}
// Mark game as completed
user.currentGame.completed = true;
user.currentGame.isActive = false;
user.currentGame.result = RESULT_FORCE_STOPPED;
user.currentGame.payout = refundAmount;
user.currentRequestId = 0;
user.requestFulfilled = false;
// ===== INTERACTIONS =====
// Refund player
gamaToken.mint(player, refundAmount);
// Add to bet history
_updateUserHistory(
user,
user.currentGame.chosenSide,
RESULT_FORCE_STOPPED,
refundAmount,
refundAmount
);
emit GameRecovered(player, requestId, refundAmount);
}
/**
* @notice Pause contract operations
*/
function pause() external onlyOwner nonReentrant {
_pause();
}
/**
* @notice Resume contract operations
*/
function unpause() external onlyOwner nonReentrant {
_unpause();
}
/**
* @notice Get player's game data
* @param player Player address
* @return gameState Current game state
* @return lastPlayed Last played timestamp
*/
function getUserData(address player) external view returns (
GameState memory gameState,
uint256 lastPlayed
) {
if (player == address(0)) revert InvalidBetParameters("Invalid player address");
UserData storage user = userData[player];
return (
user.currentGame,
user.lastPlayedTimestamp
);
}
/**
* @notice Get player's bet history
* @param player Player address
* @return Array of past bets (newest to oldest)
*/
function getBetHistory(address player) external view returns (BetHistory[] memory) {
if (player == address(0)) revert InvalidBetParameters("Invalid player address");
UserData storage user = userData[player];
uint256 length = user.recentBets.length;
if (length == 0) return new BetHistory[](0);
// Create array with exact size needed
uint256 resultLength = length > MAX_HISTORY_SIZE ? MAX_HISTORY_SIZE : length;
BetHistory[] memory orderedBets = new BetHistory[](resultLength);
// If array is not full yet
if (length < MAX_HISTORY_SIZE) {
// Copy in reverse order so newest is first
for (uint256 i = 0; i < length; i++) {
orderedBets[i] = user.recentBets[length - 1 - i];
}
} else {
// Handle circular buffer ordering
uint256 newestIndex = user.historyIndex == 0 ? MAX_HISTORY_SIZE - 1 : user.historyIndex - 1;
for (uint256 i = 0; i < MAX_HISTORY_SIZE; i++) {
orderedBets[i] = user.recentBets[(newestIndex + MAX_HISTORY_SIZE - i) % MAX_HISTORY_SIZE];
}
}
return orderedBets;
}
/**
* @notice Get player for a specific VRF request
* @param requestId VRF request ID
* @return Player address
*/
function getPlayerForRequest(uint256 requestId) external view returns (address) {
return requestToPlayer[requestId];
}
/**
* @notice Check if player has pending game
* @param player Player address
* @return Status of pending request
*/
function hasPendingRequest(address player) external view returns (bool) {
UserData storage user = userData[player];
return user.currentGame.isActive && user.currentRequestId != 0;
}
/**
* @notice Check if player can start new game
* @param player Player address
* @return Eligibility status
*/
function canStartNewGame(address player) external view returns (bool) {
UserData storage user = userData[player];
return !user.currentGame.isActive && user.currentRequestId == 0;
}
/*
* @notice Get detailed game status information
* @param player Player address
* @return Comprehensive game state and request information
*/
function getGameStatus(address player) external view returns (
bool isActive,
bool isWin,
bool isCompleted,
uint8 chosenSide,
uint256 amount,
uint8 result,
uint256 payout,
uint256 requestId,
bool requestExists,
bool requestProcessed,
bool recoveryEligible,
uint256 lastPlayTimestamp
) {
if (player == address(0)) revert InvalidBetParameters("Invalid player address");
UserData storage user = userData[player];
isActive = user.currentGame.isActive;
isCompleted = user.currentGame.completed;
chosenSide = user.currentGame.chosenSide;
amount = user.currentGame.amount;
result = user.currentGame.result;
payout = user.currentGame.payout;
requestId = user.currentRequestId;
lastPlayTimestamp = user.lastPlayedTimestamp;
// Natural win if payout > 0 and result is either HEADS or TAILS
isWin = payout > 0 && (result == HEADS || result == TAILS);
requestExists = false;
requestProcessed = false;
// Check request status if ID is valid
if (requestId != 0) {
RequestStatus storage request = s_requests[requestId];
requestExists = request.exists;
requestProcessed = request.fulfilled;
}
// Determine recovery eligibility
recoveryEligible = false;
if (isActive) {
// All conditions must be met for recovery eligibility
bool hasBlockThresholdPassed = block.number > user.lastPlayedBlock + BLOCK_THRESHOLD;
bool hasTimeoutPassed = block.timestamp > user.lastPlayedTimestamp + GAME_TIMEOUT;
bool hasVrfRequest = requestId != 0 && requestExists;
// Only eligible if ALL conditions are met
recoveryEligible = hasBlockThresholdPassed && hasTimeoutPassed && hasVrfRequest;
}
}
// ============ Private Functions ============
/**
* @dev Check if user has sufficient balance and allowance
* @param player User address
* @param amount Bet amount
*/
function _checkBalancesAndAllowances(address player, uint256 amount) private view {
if (gamaToken.balanceOf(player) < amount) {
revert InsufficientUserBalance(amount, gamaToken.balanceOf(player));
}
if (gamaToken.allowance(player, address(this)) < amount) {
revert InsufficientAllowance(amount, gamaToken.allowance(player, address(this)));
}
}
/**
* @dev Add bet to player's history using circular buffer
* @param user User data reference
* @param chosenSide Player's chosen side (HEADS or TAILS)
* @param result Flip result
* @param amount Bet amount
* @param payout Win amount
*/
function _updateUserHistory(
UserData storage user,
uint8 chosenSide,
uint8 result,
uint256 amount,
uint256 payout
) private {
BetHistory memory newBet = BetHistory({
chosenSide: chosenSide,
flippedResult: result,
amount: amount,
timestamp: uint32(block.timestamp),
payout: payout
});
if (user.recentBets.length < MAX_HISTORY_SIZE) {
// Array not full, add to end
user.recentBets.push(newBet);
user.historyIndex = uint8(user.recentBets.length % MAX_HISTORY_SIZE);
} else {
// Array full, overwrite oldest entry
user.recentBets[user.historyIndex] = newBet;
user.historyIndex = (user.historyIndex + 1) % MAX_HISTORY_SIZE;
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_gamaTokenAddress","type":"address"},{"internalType":"address","name":"vrfCoordinator","type":"address"},{"internalType":"uint64","name":"subscriptionId","type":"uint64"},{"internalType":"bytes32","name":"keyHash","type":"bytes32"},{"internalType":"uint32","name":"_callbackGasLimit","type":"uint32"},{"internalType":"uint16","name":"_requestConfirmations","type":"uint16"},{"internalType":"uint8","name":"_numWords","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"BurnFailed","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[{"internalType":"string","name":"reason","type":"string"}],"name":"GameError","type":"error"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"},{"internalType":"uint256","name":"allowed","type":"uint256"}],"name":"InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"},{"internalType":"uint256","name":"available","type":"uint256"}],"name":"InsufficientUserBalance","type":"error"},{"inputs":[{"internalType":"string","name":"reason","type":"string"}],"name":"InvalidBetParameters","type":"error"},{"inputs":[{"internalType":"uint256","name":"potentialPayout","type":"uint256"},{"internalType":"uint256","name":"maxAllowed","type":"uint256"}],"name":"MaxPayoutExceeded","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"MintFailed","type":"error"},{"inputs":[{"internalType":"address","name":"have","type":"address"},{"internalType":"address","name":"want","type":"address"}],"name":"OnlyCoordinatorCanFulfill","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"PayoutCalculationError","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferFailed","type":"error"},{"inputs":[{"internalType":"string","name":"reason","type":"string"}],"name":"VRFError","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"chosenSide","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"BetPlaced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"result","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"payout","type":"uint256"}],"name":"GameCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"refundAmount","type":"uint256"}],"name":"GameRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"HEADS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BET_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_HISTORY_SIZE","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POSSIBLE_PAYOUT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESULT_FORCE_STOPPED","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESULT_RECOVERED","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TAILS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"canStartNewGame","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"chosenSide","type":"uint8"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"flipCoin","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"forceStopGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gamaToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"getBetHistory","outputs":[{"components":[{"internalType":"uint8","name":"chosenSide","type":"uint8"},{"internalType":"uint8","name":"flippedResult","type":"uint8"},{"internalType":"uint32","name":"timestamp","type":"uint32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"payout","type":"uint256"}],"internalType":"struct BetHistory[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"getGameStatus","outputs":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"bool","name":"isWin","type":"bool"},{"internalType":"bool","name":"isCompleted","type":"bool"},{"internalType":"uint8","name":"chosenSide","type":"uint8"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint8","name":"result","type":"uint8"},{"internalType":"uint256","name":"payout","type":"uint256"},{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"bool","name":"requestExists","type":"bool"},{"internalType":"bool","name":"requestProcessed","type":"bool"},{"internalType":"bool","name":"recoveryEligible","type":"bool"},{"internalType":"uint256","name":"lastPlayTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"getPlayerForRequest","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"getUserData","outputs":[{"components":[{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"bool","name":"completed","type":"bool"},{"internalType":"uint8","name":"chosenSide","type":"uint8"},{"internalType":"uint8","name":"result","type":"uint8"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"payout","type":"uint256"}],"internalType":"struct GameState","name":"gameState","type":"tuple"},{"internalType":"uint256","name":"lastPlayed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"hasPendingRequest","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"rawFulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverOwnStuckGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_requests","outputs":[{"internalType":"bool","name":"fulfilled","type":"bool"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalGamesPlayed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalPayoutAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWageredAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
61018060405234801562000011575f80fd5b5060405162004e5338038062004e53833981810160405281019062000037919062000594565b338660015f819055508073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000e8575f6040517f1e4fbdf7000000000000000000000000000000000000000000000000000000008152600401620000df919062000653565b60405180910390fd5b620000f9816200033e60201b60201c565b505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff16036200016b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200016290620006cc565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1603620001dc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620001d3906200073a565b60405180910390fd5b5f8363ffffffff161162000227576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200021e90620007ce565b60405180910390fd5b5f8160ff16116200026f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000266906200083c565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250508573ffffffffffffffffffffffffffffffffffffffff1660c08173ffffffffffffffffffffffffffffffffffffffff16815250508467ffffffffffffffff1660e08167ffffffffffffffff16815250508361010081815250508263ffffffff166101208163ffffffff16815250508161ffff166101408161ffff16815250508060ff166101608160ff1681525050505050505050506200085c565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816001806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6200042f8262000404565b9050919050565b620004418162000423565b81146200044c575f80fd5b50565b5f815190506200045f8162000436565b92915050565b5f67ffffffffffffffff82169050919050565b620004838162000465565b81146200048e575f80fd5b50565b5f81519050620004a18162000478565b92915050565b5f819050919050565b620004bb81620004a7565b8114620004c6575f80fd5b50565b5f81519050620004d981620004b0565b92915050565b5f63ffffffff82169050919050565b620004f981620004df565b811462000504575f80fd5b50565b5f815190506200051781620004ee565b92915050565b5f61ffff82169050919050565b62000535816200051d565b811462000540575f80fd5b50565b5f8151905062000553816200052a565b92915050565b5f60ff82169050919050565b620005708162000559565b81146200057b575f80fd5b50565b5f815190506200058e8162000565565b92915050565b5f805f805f805f60e0888a031215620005b257620005b162000400565b5b5f620005c18a828b016200044f565b9750506020620005d48a828b016200044f565b9650506040620005e78a828b0162000491565b9550506060620005fa8a828b01620004c9565b94505060806200060d8a828b0162000507565b93505060a0620006208a828b0162000543565b92505060c0620006338a828b016200057e565b91505092959891949750929550565b6200064d8162000423565b82525050565b5f602082019050620006685f83018462000642565b92915050565b5f82825260208201905092915050565b7f546f6b656e20616464726573732063616e6e6f74206265207a65726f000000005f82015250565b5f620006b4601c836200066e565b9150620006c1826200067e565b602082019050919050565b5f6020820190508181035f830152620006e581620006a6565b9050919050565b7f56524620636f6f7264696e61746f722063616e6e6f74206265207a65726f00005f82015250565b5f62000722601e836200066e565b91506200072f82620006ec565b602082019050919050565b5f6020820190508181035f830152620007538162000714565b9050919050565b7f43616c6c6261636b20676173206c696d69742063616e6e6f74206265207a65725f8201527f6f00000000000000000000000000000000000000000000000000000000000000602082015250565b5f620007b66021836200066e565b9150620007c3826200075a565b604082019050919050565b5f6020820190508181035f830152620007e781620007a8565b9050919050565b7f4e756d626572206f6620776f7264732063616e6e6f74206265207a65726f00005f82015250565b5f62000824601e836200066e565b91506200083182620007ee565b602082019050919050565b5f6020820190508181035f830152620008558162000816565b9050919050565b60805160a05160c05160e0516101005161012051610140516101605161454e620009055f395f8181611d02015261227d01525f611cc001525f611ce101525f611c7e01525f611c9f01525f611c4201525f8181610e040152818161127c0152818161178101528181611acc01528181611ba20152818161278801528181612d3401528181612dd301528181612ea90152612f4a01525f81816106000152610654015261454e5ff3fe608060405234801561000f575f80fd5b50600436106101c2575f3560e01c80635d424b85116100f7578063c106625111610095578063e7efcfc21161006f578063e7efcfc2146104ba578063f2fde38b146104d8578063f3b4d4cb146104f4578063ffc9896b14610524576101c2565b8063c10662511461044e578063ce84a53b1461046c578063d56ec8c91461049c576101c2565b80638da5cb5b116100d15780638da5cb5b146103d9578063a168fa89146103f7578063a20ff9fc14610428578063b52a085514610432576101c2565b80635d424b8514610395578063715018a6146103c55780638456cb59146103cf576101c2565b80634689b4411161016457806357816f3e1161013e57806357816f3e1461031d5780635bdb2e1b1461033b5780635c492129146103595780635c975abb14610377576101c2565b80634689b441146102a65780634ca1a838146102c457806354e4437e146102e2576101c2565b80631fe543e3116101a05780631fe543e314610244578063309167f51461026057806338f8bc6d1461027e5780633f4ba83a1461029c576101c2565b806304243151146101c65780630c99375d146101f65780631995cec614610226575b5f80fd5b6101e060048036038101906101db91906131a2565b610555565b6040516101ed919061320c565b60405180910390f35b610210600480360381019061020b919061324f565b61058e565b60405161021d9190613294565b60405180910390f35b61022e6105f9565b60405161023b91906132c8565b60405180910390f35b61025e60048036038101906102599190613431565b6105fe565b005b6102686106be565b604051610275919061349a565b60405180910390f35b6102866106cd565b60405161029391906132c8565b60405180910390f35b6102a46106d2565b005b6102ae6106f4565b6040516102bb919061349a565b60405180910390f35b6102cc610703565b6040516102d991906132c8565b60405180910390f35b6102fc60048036038101906102f7919061324f565b610708565b6040516103149c9b9a999897969594939291906134b3565b60405180910390f35b61032561093f565b60405161033291906132c8565b60405180910390f35b610343610944565b604051610350919061349a565b60405180910390f35b61036161094a565b60405161036e919061349a565b60405180910390f35b61037f610950565b60405161038c9190613294565b60405180910390f35b6103af60048036038101906103aa919061324f565b610965565b6040516103bc9190613294565b60405180910390f35b6103cd6109d0565b005b6103d76109e3565b005b6103e1610a05565b6040516103ee919061320c565b60405180910390f35b610411600480360381019061040c91906131a2565b610a2c565b60405161041f92919061356b565b60405180910390f35b610430610a64565b005b61044c6004803603810190610447919061324f565b610f0a565b005b610456611383565b60405161046391906132c8565b60405180910390f35b6104866004803603810190610481919061324f565b611388565b60405161049391906136dc565b60405180910390f35b6104a461177f565b6040516104b19190613757565b60405180910390f35b6104c26117a3565b6040516104cf919061349a565b60405180910390f35b6104f260048036038101906104ed919061324f565b6117a9565b005b61050e6004803603810190610509919061379a565b61182d565b60405161051b919061349a565b60405180910390f35b61053e6004803603810190610539919061324f565b612051565b60405161054c929190613860565b60405180910390f35b5f60075f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1680156105f157505f816003015414155b915050919050565b600181565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106b057337f00000000000000000000000000000000000000000000000000000000000000006040517f1cf993f40000000000000000000000000000000000000000000000000000000081526004016106a7929190613887565b60405180910390fd5b6106ba82826121bf565b5050565b6a084595161401484a00000081565b60fe81565b6106da612875565b6106e26128fc565b6106ea612940565b6106f26129a1565b565b6a108b2a2c2802909400000081565b600281565b5f805f805f805f805f805f805f73ffffffffffffffffffffffffffffffffffffffff168d73ffffffffffffffffffffffffffffffffffffffff1603610782576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161077990613908565b60405180910390fd5b5f60025f8f73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff169c50805f015f0160019054906101000a900460ff169a50805f015f0160029054906101000a900460ff169950805f01600101549850805f015f0160039054906101000a900460ff169750805f0160020154965080600301549550806005015f9054906101000a900463ffffffff1663ffffffff1691505f871180156108705750600160ff168860ff16148061086f5750600260ff168860ff16145b5b9b505f94505f93505f86146108ba575f60065f8881526020019081526020015f209050805f0160019054906101000a900460ff169550805f015f9054906101000a900460ff169450505b5f92508c1561092f575f61012c82600601546108d69190613953565b431190505f610e10836005015f9054906101000a900463ffffffff166108fc9190613986565b63ffffffff16421190505f8089141580156109145750875b90508280156109205750815b80156109295750805b95505050505b5091939597999b5091939597999b565b60ff81565b60055481565b60035481565b5f60015f9054906101000a900460ff16905090565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff161580156109c857505f8160030154145b915050919050565b6109d8612875565b6109e15f6129aa565b565b6109eb612875565b6109f36128fc565b6109fb612a6c565b610a036129a1565b565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6006602052805f5260405f205f91509050805f015f9054906101000a900460ff1690805f0160019054906101000a900460ff16905082565b610a6c6128fc565b610a74612acd565b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610b05576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610afc90613a07565b60405180910390fd5b5f816003015490505f8103610b4f576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610b4690613a6f565b60405180910390fd5b60065f8281526020019081526020015f205f015f9054906101000a900460ff168015610b8b5750600a8260060154610b879190613953565b4311155b15610bcb576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610bc290613afd565b60405180910390fd5b5f61012c8360060154610bde9190613953565b431190505f610e10846005015f9054906101000a900463ffffffff16610c049190613986565b63ffffffff16421190505f808414158015610c3c575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b9050821580610c49575081155b80610c52575080155b15610c92576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610c8990613b8b565b60405180910390fd5b5f855f016001015490505f8103610cde576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610cd590613bf3565b60405180910390fd5b60065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f610d24919061306a565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690556001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060ff865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166340c10f1933836040518363ffffffff1660e01b8152600401610e5d929190613c11565b5f604051808303815f87803b158015610e74575f80fd5b505af1158015610e86573d5f803e3d5ffd5b50505050610eaa86875f015f0160029054906101000a900460ff1660ff8485612b0e565b3373ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d538683604051610ef2929190613c38565b60405180910390a2505050505050610f086129a1565b565b610f12612875565b610f1a6128fc565b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610fab576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610fa290613a07565b60405180910390fd5b5f816003015490505f8114158015610fdf575060065f8281526020019081526020015f205f015f9054906101000a900460ff165b8015610ffb5750600a8260060154610ff79190613953565b4311155b1561103b576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161103290613afd565b60405180910390fd5b5f61012c836006015461104e9190613953565b431190505f610e10846005015f9054906101000a900463ffffffff166110749190613986565b63ffffffff16421190505f8084141580156110ac575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b90508215806110b9575081155b806110c2575080155b15611102576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016110f990613ccf565b60405180910390fd5b5f855f016001015490505f810361114e576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161114590613bf3565b60405180910390fd5b5f85146111f15760075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff021916905560065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6111ee919061306a565b50505b6001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060fe865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166340c10f1988836040518363ffffffff1660e01b81526004016112d5929190613c11565b5f604051808303815f87803b1580156112ec575f80fd5b505af11580156112fe573d5f803e3d5ffd5b5050505061132286875f015f0160029054906101000a900460ff1660fe8485612b0e565b8673ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d53868360405161136a929190613c38565b60405180910390a25050505050506113806129a1565b50565b600a81565b60605f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036113f8576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016113ef90613908565b60405180910390fd5b5f60025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2090505f816004018054905090505f81036114a7575f67ffffffffffffffff811115611464576114636132f5565b5b60405190808252806020026020018201604052801561149d57816020015b61148a613088565b8152602001906001900390816114825790505b509250505061177a565b5f600a60ff1682116114b957816114bf565b600a60ff165b90505f8167ffffffffffffffff8111156114dc576114db6132f5565b5b60405190808252806020026020018201604052801561151557816020015b611502613088565b8152602001906001900390816114fa5790505b509050600a60ff16831015611619575f5b838110156116135784600401816001866115409190613ced565b61154a9190613ced565b8154811061155b5761155a613d20565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff168152602001600182015481526020016002820154815250508282815181106115f5576115f4613d20565b5b6020026020010181905250808061160b90613d4d565b915050611526565b50611772565b5f80856007015f9054906101000a900460ff1660ff1614611656576001856007015f9054906101000a900460ff166116519190613d94565b611665565b6001600a6116649190613d94565b5b60ff1690505f5b600a60ff1681101561176f5785600401600a60ff1682600a60ff16856116929190613953565b61169c9190613ced565b6116a69190613df5565b815481106116b7576116b6613d20565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016001820154815260200160028201548152505083828151811061175157611750613d20565b5b6020026020010181905250808061176790613d4d565b91505061166c565b50505b809450505050505b919050565b7f000000000000000000000000000000000000000000000000000000000000000081565b60045481565b6117b1612875565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603611821575f6040517f1e4fbdf7000000000000000000000000000000000000000000000000000000008152600401611818919061320c565b60405180910390fd5b61182a816129aa565b50565b5f6118366128fc565b61183e612acd565b5f8203611880576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161187790613e6f565b60405180910390fd5b6a084595161401484a0000008211156118ce576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016118c590613ed7565b60405180910390fd5b600160ff168360ff16141580156118ec5750600260ff168360ff1614155b1561192c576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161192390613f65565b60405180910390fd5b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16156119be576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016119b590613fcd565b60405180910390fd5b5f816003015414611a04576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016119fb90614035565b60405180910390fd5b611a0e3384612d31565b5f600284611a1c9190614053565b905083600282611a2c9190614094565b14611a6c576040517f327512f6000000000000000000000000000000000000000000000000000000008152600401611a639061410e565b60405180910390fd5b6a108b2a2c28029094000000811115611ac957806a108b2a2c280290940000006040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611ac0929190613c38565b60405180910390fd5b5f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16636f97e31c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b33573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b579190614140565b905080821115611ba05781816040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611b97929190613c38565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166379cc679033876040518363ffffffff1660e01b8152600401611bfb929190613c11565b5f604051808303815f87803b158015611c12575f80fd5b505af1158015611c24573d5f803e3d5ffd5b505050508460055f828254611c399190613953565b925050819055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16635d3b1d307f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006040518663ffffffff1660e01b8152600401611d41959493929190614200565b6020604051808303815f875af1158015611d5d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d819190614140565b935060405180606001604052805f151581526020016001151581526020015f67ffffffffffffffff811115611db957611db86132f5565b5b604051908082528060200260200182016040528015611de75781602001602082028036833780820191505090505b5081525060065f8681526020019081526020015f205f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151816001019080519060200190611e549291906130be565b509050503360075f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160085f8681526020019081526020015f205f6101000a81548160ff02191690831515021790555042836005015f6101000a81548163ffffffff021916908363ffffffff1602179055504383600601819055505f8360070160016101000a81548160ff0219169083151502179055506040518060c001604052806001151581526020015f151581526020018760ff1681526020015f60ff1681526020018681526020015f815250835f015f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151815f0160026101000a81548160ff021916908360ff1602179055506060820151815f0160036101000a81548160ff021916908360ff1602179055506080820151816001015560a082015181600201559050508383600301819055503373ffffffffffffffffffffffffffffffffffffffff167f1efa8a4db358fbd4b3e1004cca7c5a1bdc9de3512d3392d48a51d1961316f98385888860405161203893929190614251565b60405180910390a250505061204b6129a1565b92915050565b612059613109565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036120c8576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016120bf90613908565b60405180910390fd5b5f60025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f01816005015f9054906101000a900463ffffffff16816040518060c00160405290815f82015f9054906101000a900460ff161515151581526020015f820160019054906101000a900460ff161515151581526020015f820160029054906101000a900460ff1660ff1660ff1681526020015f820160039054906101000a900460ff1660ff1660ff1681526020016001820154815260200160028201548152505091508063ffffffff1690509250925050915091565b6121c76128fc565b5f60065f8481526020019081526020015f209050805f0160019054906101000a900460ff1661222b576040517f1726a07e000000000000000000000000000000000000000000000000000000008152600401612222906142d0565b60405180910390fd5b805f015f9054906101000a900460ff161561227b576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161227290614338565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000060ff168251146122e1576040517f1726a07e0000000000000000000000000000000000000000000000000000000081526004016122d8906143a0565b60405180910390fd5b5f60075f8581526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603612383576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161237a90613908565b60405180910390fd5b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20905084816003015414612409576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161240090614408565b60405180910390fd5b6001835f015f6101000a81548160ff0219169083151502179055508383600101908051906020019061243c9291906130be565b5060018160070160016101000a81548160ff021916908315150217905550805f015f015f9054906101000a900460ff166125385760065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6124b6919061306a565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690555f81600301819055505f8160070160016101000a81548160ff021916908315150217905550505050612869565b5f815f015f0160029054906101000a900460ff1690505f825f016001015490505f6001600260ff16885f8151811061257357612572613d20565b5b60200260200101516125859190613df5565b61258f9190613953565b90505f8160ff168460ff160361261e5760027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6125cc9190614094565b83111561260e576040517f327512f600000000000000000000000000000000000000000000000000000000815260040161260590614496565b60405180910390fd5b60028361261b9190614053565b90505b81855f015f0160036101000a81548160ff021916908360ff1602179055505f855f015f015f6101000a81548160ff0219169083151502179055506001855f015f0160016101000a81548160ff02191690831515021790555080855f016002018190555061268e8585848685612b0e565b5f8111156126af578060045f8282546126a79190613953565b925050819055505b60035f81546001019190508190555060075f8a81526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8a81526020019081526020015f205f6101000a81549060ff021916905560065f8a81526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f612757919061306a565b50505f85600301819055505f8560070160016101000a81548160ff0219169083151502179055505f81111561280f577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166340c10f1987836040518363ffffffff1660e01b81526004016127e1929190613c11565b5f604051808303815f87803b1580156127f8575f80fd5b505af115801561280a573d5f803e3d5ffd5b505050505b8573ffffffffffffffffffffffffffffffffffffffff167fc74777e2214d32e7b4952c2ecbbfc0ec7dc3642e448c3c927261cb69d9f036508a848460405161285993929190614251565b60405180910390a2505050505050505b6128716129a1565b5050565b61287d613023565b73ffffffffffffffffffffffffffffffffffffffff1661289b610a05565b73ffffffffffffffffffffffffffffffffffffffff16146128fa576128be613023565b6040517f118cdaa70000000000000000000000000000000000000000000000000000000081526004016128f1919061320c565b60405180910390fd5b565b60025f5403612937576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60025f81905550565b61294861302a565b5f60015f6101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa61298a613023565b604051612997919061320c565b60405180910390a1565b60015f81905550565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816001806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b612a74612acd565b6001805f6101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612ab6613023565b604051612ac3919061320c565b60405180910390a1565b612ad5610950565b15612b0c576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f6040518060a001604052808660ff1681526020018560ff1681526020014263ffffffff168152602001848152602001838152509050600a60ff1686600401805490501015612c33578560040181908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff16021790555060608201518160010155608082015181600201555050600a60ff168660040180549050612c139190613df5565b866007015f6101000a81548160ff021916908360ff160217905550612d29565b8086600401876007015f9054906101000a900460ff1660ff1681548110612c5d57612c5c613d20565b5b905f5260205f2090600302015f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff1602179055506060820151816001015560808201518160020155905050600a6001876007015f9054906101000a900460ff16612d0391906144b4565b612d0d91906144e8565b866007015f6101000a81548160ff021916908360ff1602179055505b505050505050565b807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612d8b919061320c565b602060405180830381865afa158015612da6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612dca9190614140565b1015612ea657807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612e2a919061320c565b602060405180830381865afa158015612e45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e699190614140565b6040517f5f504d20000000000000000000000000000000000000000000000000000000008152600401612e9d929190613c38565b60405180910390fd5b807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612f02929190613887565b602060405180830381865afa158015612f1d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f419190614140565b101561301f57807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612fa3929190613887565b602060405180830381865afa158015612fbe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fe29190614140565b6040517f2a1b2dd8000000000000000000000000000000000000000000000000000000008152600401613016929190613c38565b60405180910390fd5b5050565b5f33905090565b613032610950565b613068576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5080545f8255905f5260205f20908101906130859190613143565b50565b6040518060a001604052805f60ff1681526020015f60ff1681526020015f63ffffffff1681526020015f81526020015f81525090565b828054828255905f5260205f209081019282156130f8579160200282015b828111156130f75782518255916020019190600101906130dc565b5b5090506131059190613143565b5090565b6040518060c001604052805f151581526020015f151581526020015f60ff1681526020015f60ff1681526020015f81526020015f81525090565b5b8082111561315a575f815f905550600101613144565b5090565b5f604051905090565b5f80fd5b5f80fd5b5f819050919050565b6131818161316f565b811461318b575f80fd5b50565b5f8135905061319c81613178565b92915050565b5f602082840312156131b7576131b6613167565b5b5f6131c48482850161318e565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6131f6826131cd565b9050919050565b613206816131ec565b82525050565b5f60208201905061321f5f8301846131fd565b92915050565b61322e816131ec565b8114613238575f80fd5b50565b5f8135905061324981613225565b92915050565b5f6020828403121561326457613263613167565b5b5f6132718482850161323b565b91505092915050565b5f8115159050919050565b61328e8161327a565b82525050565b5f6020820190506132a75f830184613285565b92915050565b5f60ff82169050919050565b6132c2816132ad565b82525050565b5f6020820190506132db5f8301846132b9565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61332b826132e5565b810181811067ffffffffffffffff8211171561334a576133496132f5565b5b80604052505050565b5f61335c61315e565b90506133688282613322565b919050565b5f67ffffffffffffffff821115613387576133866132f5565b5b602082029050602081019050919050565b5f80fd5b5f6133ae6133a98461336d565b613353565b905080838252602082019050602084028301858111156133d1576133d0613398565b5b835b818110156133fa57806133e6888261318e565b8452602084019350506020810190506133d3565b5050509392505050565b5f82601f830112613418576134176132e1565b5b813561342884826020860161339c565b91505092915050565b5f806040838503121561344757613446613167565b5b5f6134548582860161318e565b925050602083013567ffffffffffffffff8111156134755761347461316b565b5b61348185828601613404565b9150509250929050565b6134948161316f565b82525050565b5f6020820190506134ad5f83018461348b565b92915050565b5f610180820190506134c75f83018f613285565b6134d4602083018e613285565b6134e1604083018d613285565b6134ee606083018c6132b9565b6134fb608083018b61348b565b61350860a083018a6132b9565b61351560c083018961348b565b61352260e083018861348b565b613530610100830187613285565b61353e610120830186613285565b61354c610140830185613285565b61355a61016083018461348b565b9d9c50505050505050505050505050565b5f60408201905061357e5f830185613285565b61358b6020830184613285565b9392505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6135c4816132ad565b82525050565b5f63ffffffff82169050919050565b6135e2816135ca565b82525050565b6135f18161316f565b82525050565b60a082015f82015161360b5f8501826135bb565b50602082015161361e60208501826135bb565b50604082015161363160408501826135d9565b50606082015161364460608501826135e8565b50608082015161365760808501826135e8565b50505050565b5f61366883836135f7565b60a08301905092915050565b5f602082019050919050565b5f61368a82613592565b613694818561359c565b935061369f836135ac565b805f5b838110156136cf5781516136b6888261365d565b97506136c183613674565b9250506001810190506136a2565b5085935050505092915050565b5f6020820190508181035f8301526136f48184613680565b905092915050565b5f819050919050565b5f61371f61371a613715846131cd565b6136fc565b6131cd565b9050919050565b5f61373082613705565b9050919050565b5f61374182613726565b9050919050565b61375181613737565b82525050565b5f60208201905061376a5f830184613748565b92915050565b613779816132ad565b8114613783575f80fd5b50565b5f8135905061379481613770565b92915050565b5f80604083850312156137b0576137af613167565b5b5f6137bd85828601613786565b92505060206137ce8582860161318e565b9150509250929050565b6137e18161327a565b82525050565b60c082015f8201516137fb5f8501826137d8565b50602082015161380e60208501826137d8565b50604082015161382160408501826135bb565b50606082015161383460608501826135bb565b50608082015161384760808501826135e8565b5060a082015161385a60a08501826135e8565b50505050565b5f60e0820190506138735f8301856137e7565b61388060c083018461348b565b9392505050565b5f60408201905061389a5f8301856131fd565b6138a760208301846131fd565b9392505050565b5f82825260208201905092915050565b7f496e76616c696420706c617965722061646472657373000000000000000000005f82015250565b5f6138f26016836138ae565b91506138fd826138be565b602082019050919050565b5f6020820190508181035f83015261391f816138e6565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61395d8261316f565b91506139688361316f565b92508282019050808211156139805761397f613926565b5b92915050565b5f613990826135ca565b915061399b836135ca565b9250828201905063ffffffff8111156139b7576139b6613926565b5b92915050565b7f4e6f206163746976652067616d650000000000000000000000000000000000005f82015250565b5f6139f1600e836138ae565b91506139fc826139bd565b602082019050919050565b5f6020820190508181035f830152613a1e816139e5565b9050919050565b7f4e6f2070656e64696e67207265717565737420746f207265636f7665720000005f82015250565b5f613a59601d836138ae565b9150613a6482613a25565b602082019050919050565b5f6020820190508181035f830152613a8681613a4d565b9050919050565b7f52657175657374206a7573742066756c66696c6c65642c206c657420565246205f8201527f636f6d706c657465000000000000000000000000000000000000000000000000602082015250565b5f613ae76028836138ae565b9150613af282613a8d565b604082019050919050565b5f6020820190508181035f830152613b1481613adb565b9050919050565b7f47616d65206e6f7420656c696769626c6520666f72207265636f7665727920795f8201527f6574000000000000000000000000000000000000000000000000000000000000602082015250565b5f613b756022836138ae565b9150613b8082613b1b565b604082019050919050565b5f6020820190508181035f830152613ba281613b69565b9050919050565b7f4e6f7468696e6720746f20726566756e640000000000000000000000000000005f82015250565b5f613bdd6011836138ae565b9150613be882613ba9565b602082019050919050565b5f6020820190508181035f830152613c0a81613bd1565b9050919050565b5f604082019050613c245f8301856131fd565b613c31602083018461348b565b9392505050565b5f604082019050613c4b5f83018561348b565b613c58602083018461348b565b9392505050565b7f47616d65206e6f7420656c696769626c6520666f7220666f7263652073746f705f8201527f2079657400000000000000000000000000000000000000000000000000000000602082015250565b5f613cb96024836138ae565b9150613cc482613c5f565b604082019050919050565b5f6020820190508181035f830152613ce681613cad565b9050919050565b5f613cf78261316f565b9150613d028361316f565b9250828203905081811115613d1a57613d19613926565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f613d578261316f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613d8957613d88613926565b5b600182019050919050565b5f613d9e826132ad565b9150613da9836132ad565b9250828203905060ff811115613dc257613dc1613926565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f613dff8261316f565b9150613e0a8361316f565b925082613e1a57613e19613dc8565b5b828206905092915050565b7f42657420616d6f756e742063616e6e6f74206265207a65726f000000000000005f82015250565b5f613e596019836138ae565b9150613e6482613e25565b602082019050919050565b5f6020820190508181035f830152613e8681613e4d565b9050919050565b7f42657420616d6f756e7420746f6f206c617267650000000000000000000000005f82015250565b5f613ec16014836138ae565b9150613ecc82613e8d565b602082019050919050565b5f6020820190508181035f830152613eee81613eb5565b9050919050565b7f496e76616c69642063686f73656e207369646520286d757374206265203120665f8201527f6f72204845414453206f72203220666f72205441494c53290000000000000000602082015250565b5f613f4f6038836138ae565b9150613f5a82613ef5565b604082019050919050565b5f6020820190508181035f830152613f7c81613f43565b9050919050565b7f557365722068617320616e206163746976652067616d650000000000000000005f82015250565b5f613fb76017836138ae565b9150613fc282613f83565b602082019050919050565b5f6020820190508181035f830152613fe481613fab565b9050919050565b7f557365722068617320612070656e64696e6720726571756573740000000000005f82015250565b5f61401f601a836138ae565b915061402a82613feb565b602082019050919050565b5f6020820190508181035f83015261404c81614013565b9050919050565b5f61405d8261316f565b91506140688361316f565b92508282026140768161316f565b9150828204841483151761408d5761408c613926565b5b5092915050565b5f61409e8261316f565b91506140a98361316f565b9250826140b9576140b8613dc8565b5b828204905092915050565b7f5061796f75742063616c63756c6174696f6e206f766572666c6f7700000000005f82015250565b5f6140f8601b836138ae565b9150614103826140c4565b602082019050919050565b5f6020820190508181035f830152614125816140ec565b9050919050565b5f8151905061413a81613178565b92915050565b5f6020828403121561415557614154613167565b5b5f6141628482850161412c565b91505092915050565b5f819050919050565b61417d8161416b565b82525050565b5f67ffffffffffffffff82169050919050565b61419f81614183565b82525050565b5f61ffff82169050919050565b6141bb816141a5565b82525050565b6141ca816135ca565b82525050565b5f6141ea6141e56141e0846132ad565b6136fc565b6135ca565b9050919050565b6141fa816141d0565b82525050565b5f60a0820190506142135f830188614174565b6142206020830187614196565b61422d60408301866141b2565b61423a60608301856141c1565b61424760808301846141f1565b9695505050505050565b5f6060820190506142645f83018661348b565b61427160208301856132b9565b61427e604083018461348b565b949350505050565b7f52657175657374206e6f7420666f756e640000000000000000000000000000005f82015250565b5f6142ba6011836138ae565b91506142c582614286565b602082019050919050565b5f6020820190508181035f8301526142e7816142ae565b9050919050565b7f5265717565737420616c72656164792066756c66696c6c6564000000000000005f82015250565b5f6143226019836138ae565b915061432d826142ee565b602082019050919050565b5f6020820190508181035f83015261434f81614316565b9050919050565b7f496e76616c69642072616e646f6d20776f726473206c656e67746800000000005f82015250565b5f61438a601b836138ae565b915061439582614356565b602082019050919050565b5f6020820190508181035f8301526143b78161437e565b9050919050565b7f52657175657374204944206d69736d61746368000000000000000000000000005f82015250565b5f6143f26013836138ae565b91506143fd826143be565b602082019050919050565b5f6020820190508181035f83015261441f816143e6565b9050919050565b7f42657420616d6f756e7420746f6f206c6172676520666f72207061796f7574205f8201527f63616c63756c6174696f6e000000000000000000000000000000000000000000602082015250565b5f614480602b836138ae565b915061448b82614426565b604082019050919050565b5f6020820190508181035f8301526144ad81614474565b9050919050565b5f6144be826132ad565b91506144c9836132ad565b9250828201905060ff8111156144e2576144e1613926565b5b92915050565b5f6144f2826132ad565b91506144fd836132ad565b92508261450d5761450c613dc8565b5b82820690509291505056fea26469706673582212203e818d623af3e238fd9d52509a69be771a4adb8114d3e2519f4dd447a4e7552364736f6c634300081400330000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f0000000000000000000000000000000000000000000000000000000000000026c0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af00000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001
Deployed Bytecode
0x608060405234801561000f575f80fd5b50600436106101c2575f3560e01c80635d424b85116100f7578063c106625111610095578063e7efcfc21161006f578063e7efcfc2146104ba578063f2fde38b146104d8578063f3b4d4cb146104f4578063ffc9896b14610524576101c2565b8063c10662511461044e578063ce84a53b1461046c578063d56ec8c91461049c576101c2565b80638da5cb5b116100d15780638da5cb5b146103d9578063a168fa89146103f7578063a20ff9fc14610428578063b52a085514610432576101c2565b80635d424b8514610395578063715018a6146103c55780638456cb59146103cf576101c2565b80634689b4411161016457806357816f3e1161013e57806357816f3e1461031d5780635bdb2e1b1461033b5780635c492129146103595780635c975abb14610377576101c2565b80634689b441146102a65780634ca1a838146102c457806354e4437e146102e2576101c2565b80631fe543e3116101a05780631fe543e314610244578063309167f51461026057806338f8bc6d1461027e5780633f4ba83a1461029c576101c2565b806304243151146101c65780630c99375d146101f65780631995cec614610226575b5f80fd5b6101e060048036038101906101db91906131a2565b610555565b6040516101ed919061320c565b60405180910390f35b610210600480360381019061020b919061324f565b61058e565b60405161021d9190613294565b60405180910390f35b61022e6105f9565b60405161023b91906132c8565b60405180910390f35b61025e60048036038101906102599190613431565b6105fe565b005b6102686106be565b604051610275919061349a565b60405180910390f35b6102866106cd565b60405161029391906132c8565b60405180910390f35b6102a46106d2565b005b6102ae6106f4565b6040516102bb919061349a565b60405180910390f35b6102cc610703565b6040516102d991906132c8565b60405180910390f35b6102fc60048036038101906102f7919061324f565b610708565b6040516103149c9b9a999897969594939291906134b3565b60405180910390f35b61032561093f565b60405161033291906132c8565b60405180910390f35b610343610944565b604051610350919061349a565b60405180910390f35b61036161094a565b60405161036e919061349a565b60405180910390f35b61037f610950565b60405161038c9190613294565b60405180910390f35b6103af60048036038101906103aa919061324f565b610965565b6040516103bc9190613294565b60405180910390f35b6103cd6109d0565b005b6103d76109e3565b005b6103e1610a05565b6040516103ee919061320c565b60405180910390f35b610411600480360381019061040c91906131a2565b610a2c565b60405161041f92919061356b565b60405180910390f35b610430610a64565b005b61044c6004803603810190610447919061324f565b610f0a565b005b610456611383565b60405161046391906132c8565b60405180910390f35b6104866004803603810190610481919061324f565b611388565b60405161049391906136dc565b60405180910390f35b6104a461177f565b6040516104b19190613757565b60405180910390f35b6104c26117a3565b6040516104cf919061349a565b60405180910390f35b6104f260048036038101906104ed919061324f565b6117a9565b005b61050e6004803603810190610509919061379a565b61182d565b60405161051b919061349a565b60405180910390f35b61053e6004803603810190610539919061324f565b612051565b60405161054c929190613860565b60405180910390f35b5f60075f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1680156105f157505f816003015414155b915050919050565b600181565b7f000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106b057337f000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f6040517f1cf993f40000000000000000000000000000000000000000000000000000000081526004016106a7929190613887565b60405180910390fd5b6106ba82826121bf565b5050565b6a084595161401484a00000081565b60fe81565b6106da612875565b6106e26128fc565b6106ea612940565b6106f26129a1565b565b6a108b2a2c2802909400000081565b600281565b5f805f805f805f805f805f805f73ffffffffffffffffffffffffffffffffffffffff168d73ffffffffffffffffffffffffffffffffffffffff1603610782576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161077990613908565b60405180910390fd5b5f60025f8f73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff169c50805f015f0160019054906101000a900460ff169a50805f015f0160029054906101000a900460ff169950805f01600101549850805f015f0160039054906101000a900460ff169750805f0160020154965080600301549550806005015f9054906101000a900463ffffffff1663ffffffff1691505f871180156108705750600160ff168860ff16148061086f5750600260ff168860ff16145b5b9b505f94505f93505f86146108ba575f60065f8881526020019081526020015f209050805f0160019054906101000a900460ff169550805f015f9054906101000a900460ff169450505b5f92508c1561092f575f61012c82600601546108d69190613953565b431190505f610e10836005015f9054906101000a900463ffffffff166108fc9190613986565b63ffffffff16421190505f8089141580156109145750875b90508280156109205750815b80156109295750805b95505050505b5091939597999b5091939597999b565b60ff81565b60055481565b60035481565b5f60015f9054906101000a900460ff16905090565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff161580156109c857505f8160030154145b915050919050565b6109d8612875565b6109e15f6129aa565b565b6109eb612875565b6109f36128fc565b6109fb612a6c565b610a036129a1565b565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6006602052805f5260405f205f91509050805f015f9054906101000a900460ff1690805f0160019054906101000a900460ff16905082565b610a6c6128fc565b610a74612acd565b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610b05576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610afc90613a07565b60405180910390fd5b5f816003015490505f8103610b4f576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610b4690613a6f565b60405180910390fd5b60065f8281526020019081526020015f205f015f9054906101000a900460ff168015610b8b5750600a8260060154610b879190613953565b4311155b15610bcb576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610bc290613afd565b60405180910390fd5b5f61012c8360060154610bde9190613953565b431190505f610e10846005015f9054906101000a900463ffffffff16610c049190613986565b63ffffffff16421190505f808414158015610c3c575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b9050821580610c49575081155b80610c52575080155b15610c92576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610c8990613b8b565b60405180910390fd5b5f855f016001015490505f8103610cde576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610cd590613bf3565b60405180910390fd5b60065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f610d24919061306a565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690556001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060ff865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166340c10f1933836040518363ffffffff1660e01b8152600401610e5d929190613c11565b5f604051808303815f87803b158015610e74575f80fd5b505af1158015610e86573d5f803e3d5ffd5b50505050610eaa86875f015f0160029054906101000a900460ff1660ff8485612b0e565b3373ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d538683604051610ef2929190613c38565b60405180910390a2505050505050610f086129a1565b565b610f12612875565b610f1a6128fc565b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610fab576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610fa290613a07565b60405180910390fd5b5f816003015490505f8114158015610fdf575060065f8281526020019081526020015f205f015f9054906101000a900460ff165b8015610ffb5750600a8260060154610ff79190613953565b4311155b1561103b576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161103290613afd565b60405180910390fd5b5f61012c836006015461104e9190613953565b431190505f610e10846005015f9054906101000a900463ffffffff166110749190613986565b63ffffffff16421190505f8084141580156110ac575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b90508215806110b9575081155b806110c2575080155b15611102576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016110f990613ccf565b60405180910390fd5b5f855f016001015490505f810361114e576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161114590613bf3565b60405180910390fd5b5f85146111f15760075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff021916905560065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6111ee919061306a565b50505b6001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060fe865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166340c10f1988836040518363ffffffff1660e01b81526004016112d5929190613c11565b5f604051808303815f87803b1580156112ec575f80fd5b505af11580156112fe573d5f803e3d5ffd5b5050505061132286875f015f0160029054906101000a900460ff1660fe8485612b0e565b8673ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d53868360405161136a929190613c38565b60405180910390a25050505050506113806129a1565b50565b600a81565b60605f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036113f8576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016113ef90613908565b60405180910390fd5b5f60025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2090505f816004018054905090505f81036114a7575f67ffffffffffffffff811115611464576114636132f5565b5b60405190808252806020026020018201604052801561149d57816020015b61148a613088565b8152602001906001900390816114825790505b509250505061177a565b5f600a60ff1682116114b957816114bf565b600a60ff165b90505f8167ffffffffffffffff8111156114dc576114db6132f5565b5b60405190808252806020026020018201604052801561151557816020015b611502613088565b8152602001906001900390816114fa5790505b509050600a60ff16831015611619575f5b838110156116135784600401816001866115409190613ced565b61154a9190613ced565b8154811061155b5761155a613d20565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff168152602001600182015481526020016002820154815250508282815181106115f5576115f4613d20565b5b6020026020010181905250808061160b90613d4d565b915050611526565b50611772565b5f80856007015f9054906101000a900460ff1660ff1614611656576001856007015f9054906101000a900460ff166116519190613d94565b611665565b6001600a6116649190613d94565b5b60ff1690505f5b600a60ff1681101561176f5785600401600a60ff1682600a60ff16856116929190613953565b61169c9190613ced565b6116a69190613df5565b815481106116b7576116b6613d20565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016001820154815260200160028201548152505083828151811061175157611750613d20565b5b6020026020010181905250808061176790613d4d565b91505061166c565b50505b809450505050505b919050565b7f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b81565b60045481565b6117b1612875565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603611821575f6040517f1e4fbdf7000000000000000000000000000000000000000000000000000000008152600401611818919061320c565b60405180910390fd5b61182a816129aa565b50565b5f6118366128fc565b61183e612acd565b5f8203611880576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161187790613e6f565b60405180910390fd5b6a084595161401484a0000008211156118ce576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016118c590613ed7565b60405180910390fd5b600160ff168360ff16141580156118ec5750600260ff168360ff1614155b1561192c576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161192390613f65565b60405180910390fd5b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16156119be576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016119b590613fcd565b60405180910390fd5b5f816003015414611a04576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016119fb90614035565b60405180910390fd5b611a0e3384612d31565b5f600284611a1c9190614053565b905083600282611a2c9190614094565b14611a6c576040517f327512f6000000000000000000000000000000000000000000000000000000008152600401611a639061410e565b60405180910390fd5b6a108b2a2c28029094000000811115611ac957806a108b2a2c280290940000006040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611ac0929190613c38565b60405180910390fd5b5f7f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff16636f97e31c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b33573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b579190614140565b905080821115611ba05781816040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611b97929190613c38565b60405180910390fd5b7f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166379cc679033876040518363ffffffff1660e01b8152600401611bfb929190613c11565b5f604051808303815f87803b158015611c12575f80fd5b505af1158015611c24573d5f803e3d5ffd5b505050508460055f828254611c399190613953565b925050819055507f000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f73ffffffffffffffffffffffffffffffffffffffff16635d3b1d307fc0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af7f00000000000000000000000000000000000000000000000000000000000000267f00000000000000000000000000000000000000000000000000000000000000037f00000000000000000000000000000000000000000000000000000000001e84807f00000000000000000000000000000000000000000000000000000000000000016040518663ffffffff1660e01b8152600401611d41959493929190614200565b6020604051808303815f875af1158015611d5d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d819190614140565b935060405180606001604052805f151581526020016001151581526020015f67ffffffffffffffff811115611db957611db86132f5565b5b604051908082528060200260200182016040528015611de75781602001602082028036833780820191505090505b5081525060065f8681526020019081526020015f205f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151816001019080519060200190611e549291906130be565b509050503360075f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160085f8681526020019081526020015f205f6101000a81548160ff02191690831515021790555042836005015f6101000a81548163ffffffff021916908363ffffffff1602179055504383600601819055505f8360070160016101000a81548160ff0219169083151502179055506040518060c001604052806001151581526020015f151581526020018760ff1681526020015f60ff1681526020018681526020015f815250835f015f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151815f0160026101000a81548160ff021916908360ff1602179055506060820151815f0160036101000a81548160ff021916908360ff1602179055506080820151816001015560a082015181600201559050508383600301819055503373ffffffffffffffffffffffffffffffffffffffff167f1efa8a4db358fbd4b3e1004cca7c5a1bdc9de3512d3392d48a51d1961316f98385888860405161203893929190614251565b60405180910390a250505061204b6129a1565b92915050565b612059613109565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036120c8576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016120bf90613908565b60405180910390fd5b5f60025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f01816005015f9054906101000a900463ffffffff16816040518060c00160405290815f82015f9054906101000a900460ff161515151581526020015f820160019054906101000a900460ff161515151581526020015f820160029054906101000a900460ff1660ff1660ff1681526020015f820160039054906101000a900460ff1660ff1660ff1681526020016001820154815260200160028201548152505091508063ffffffff1690509250925050915091565b6121c76128fc565b5f60065f8481526020019081526020015f209050805f0160019054906101000a900460ff1661222b576040517f1726a07e000000000000000000000000000000000000000000000000000000008152600401612222906142d0565b60405180910390fd5b805f015f9054906101000a900460ff161561227b576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161227290614338565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000160ff168251146122e1576040517f1726a07e0000000000000000000000000000000000000000000000000000000081526004016122d8906143a0565b60405180910390fd5b5f60075f8581526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603612383576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161237a90613908565b60405180910390fd5b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20905084816003015414612409576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161240090614408565b60405180910390fd5b6001835f015f6101000a81548160ff0219169083151502179055508383600101908051906020019061243c9291906130be565b5060018160070160016101000a81548160ff021916908315150217905550805f015f015f9054906101000a900460ff166125385760065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6124b6919061306a565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690555f81600301819055505f8160070160016101000a81548160ff021916908315150217905550505050612869565b5f815f015f0160029054906101000a900460ff1690505f825f016001015490505f6001600260ff16885f8151811061257357612572613d20565b5b60200260200101516125859190613df5565b61258f9190613953565b90505f8160ff168460ff160361261e5760027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6125cc9190614094565b83111561260e576040517f327512f600000000000000000000000000000000000000000000000000000000815260040161260590614496565b60405180910390fd5b60028361261b9190614053565b90505b81855f015f0160036101000a81548160ff021916908360ff1602179055505f855f015f015f6101000a81548160ff0219169083151502179055506001855f015f0160016101000a81548160ff02191690831515021790555080855f016002018190555061268e8585848685612b0e565b5f8111156126af578060045f8282546126a79190613953565b925050819055505b60035f81546001019190508190555060075f8a81526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8a81526020019081526020015f205f6101000a81549060ff021916905560065f8a81526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f612757919061306a565b50505f85600301819055505f8560070160016101000a81548160ff0219169083151502179055505f81111561280f577f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166340c10f1987836040518363ffffffff1660e01b81526004016127e1929190613c11565b5f604051808303815f87803b1580156127f8575f80fd5b505af115801561280a573d5f803e3d5ffd5b505050505b8573ffffffffffffffffffffffffffffffffffffffff167fc74777e2214d32e7b4952c2ecbbfc0ec7dc3642e448c3c927261cb69d9f036508a848460405161285993929190614251565b60405180910390a2505050505050505b6128716129a1565b5050565b61287d613023565b73ffffffffffffffffffffffffffffffffffffffff1661289b610a05565b73ffffffffffffffffffffffffffffffffffffffff16146128fa576128be613023565b6040517f118cdaa70000000000000000000000000000000000000000000000000000000081526004016128f1919061320c565b60405180910390fd5b565b60025f5403612937576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60025f81905550565b61294861302a565b5f60015f6101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa61298a613023565b604051612997919061320c565b60405180910390a1565b60015f81905550565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816001806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b612a74612acd565b6001805f6101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612ab6613023565b604051612ac3919061320c565b60405180910390a1565b612ad5610950565b15612b0c576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f6040518060a001604052808660ff1681526020018560ff1681526020014263ffffffff168152602001848152602001838152509050600a60ff1686600401805490501015612c33578560040181908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff16021790555060608201518160010155608082015181600201555050600a60ff168660040180549050612c139190613df5565b866007015f6101000a81548160ff021916908360ff160217905550612d29565b8086600401876007015f9054906101000a900460ff1660ff1681548110612c5d57612c5c613d20565b5b905f5260205f2090600302015f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff1602179055506060820151816001015560808201518160020155905050600a6001876007015f9054906101000a900460ff16612d0391906144b4565b612d0d91906144e8565b866007015f6101000a81548160ff021916908360ff1602179055505b505050505050565b807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612d8b919061320c565b602060405180830381865afa158015612da6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612dca9190614140565b1015612ea657807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612e2a919061320c565b602060405180830381865afa158015612e45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e699190614140565b6040517f5f504d20000000000000000000000000000000000000000000000000000000008152600401612e9d929190613c38565b60405180910390fd5b807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612f02929190613887565b602060405180830381865afa158015612f1d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f419190614140565b101561301f57807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612fa3929190613887565b602060405180830381865afa158015612fbe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fe29190614140565b6040517f2a1b2dd8000000000000000000000000000000000000000000000000000000008152600401613016929190613c38565b60405180910390fd5b5050565b5f33905090565b613032610950565b613068576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5080545f8255905f5260205f20908101906130859190613143565b50565b6040518060a001604052805f60ff1681526020015f60ff1681526020015f63ffffffff1681526020015f81526020015f81525090565b828054828255905f5260205f209081019282156130f8579160200282015b828111156130f75782518255916020019190600101906130dc565b5b5090506131059190613143565b5090565b6040518060c001604052805f151581526020015f151581526020015f60ff1681526020015f60ff1681526020015f81526020015f81525090565b5b8082111561315a575f815f905550600101613144565b5090565b5f604051905090565b5f80fd5b5f80fd5b5f819050919050565b6131818161316f565b811461318b575f80fd5b50565b5f8135905061319c81613178565b92915050565b5f602082840312156131b7576131b6613167565b5b5f6131c48482850161318e565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6131f6826131cd565b9050919050565b613206816131ec565b82525050565b5f60208201905061321f5f8301846131fd565b92915050565b61322e816131ec565b8114613238575f80fd5b50565b5f8135905061324981613225565b92915050565b5f6020828403121561326457613263613167565b5b5f6132718482850161323b565b91505092915050565b5f8115159050919050565b61328e8161327a565b82525050565b5f6020820190506132a75f830184613285565b92915050565b5f60ff82169050919050565b6132c2816132ad565b82525050565b5f6020820190506132db5f8301846132b9565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61332b826132e5565b810181811067ffffffffffffffff8211171561334a576133496132f5565b5b80604052505050565b5f61335c61315e565b90506133688282613322565b919050565b5f67ffffffffffffffff821115613387576133866132f5565b5b602082029050602081019050919050565b5f80fd5b5f6133ae6133a98461336d565b613353565b905080838252602082019050602084028301858111156133d1576133d0613398565b5b835b818110156133fa57806133e6888261318e565b8452602084019350506020810190506133d3565b5050509392505050565b5f82601f830112613418576134176132e1565b5b813561342884826020860161339c565b91505092915050565b5f806040838503121561344757613446613167565b5b5f6134548582860161318e565b925050602083013567ffffffffffffffff8111156134755761347461316b565b5b61348185828601613404565b9150509250929050565b6134948161316f565b82525050565b5f6020820190506134ad5f83018461348b565b92915050565b5f610180820190506134c75f83018f613285565b6134d4602083018e613285565b6134e1604083018d613285565b6134ee606083018c6132b9565b6134fb608083018b61348b565b61350860a083018a6132b9565b61351560c083018961348b565b61352260e083018861348b565b613530610100830187613285565b61353e610120830186613285565b61354c610140830185613285565b61355a61016083018461348b565b9d9c50505050505050505050505050565b5f60408201905061357e5f830185613285565b61358b6020830184613285565b9392505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6135c4816132ad565b82525050565b5f63ffffffff82169050919050565b6135e2816135ca565b82525050565b6135f18161316f565b82525050565b60a082015f82015161360b5f8501826135bb565b50602082015161361e60208501826135bb565b50604082015161363160408501826135d9565b50606082015161364460608501826135e8565b50608082015161365760808501826135e8565b50505050565b5f61366883836135f7565b60a08301905092915050565b5f602082019050919050565b5f61368a82613592565b613694818561359c565b935061369f836135ac565b805f5b838110156136cf5781516136b6888261365d565b97506136c183613674565b9250506001810190506136a2565b5085935050505092915050565b5f6020820190508181035f8301526136f48184613680565b905092915050565b5f819050919050565b5f61371f61371a613715846131cd565b6136fc565b6131cd565b9050919050565b5f61373082613705565b9050919050565b5f61374182613726565b9050919050565b61375181613737565b82525050565b5f60208201905061376a5f830184613748565b92915050565b613779816132ad565b8114613783575f80fd5b50565b5f8135905061379481613770565b92915050565b5f80604083850312156137b0576137af613167565b5b5f6137bd85828601613786565b92505060206137ce8582860161318e565b9150509250929050565b6137e18161327a565b82525050565b60c082015f8201516137fb5f8501826137d8565b50602082015161380e60208501826137d8565b50604082015161382160408501826135bb565b50606082015161383460608501826135bb565b50608082015161384760808501826135e8565b5060a082015161385a60a08501826135e8565b50505050565b5f60e0820190506138735f8301856137e7565b61388060c083018461348b565b9392505050565b5f60408201905061389a5f8301856131fd565b6138a760208301846131fd565b9392505050565b5f82825260208201905092915050565b7f496e76616c696420706c617965722061646472657373000000000000000000005f82015250565b5f6138f26016836138ae565b91506138fd826138be565b602082019050919050565b5f6020820190508181035f83015261391f816138e6565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61395d8261316f565b91506139688361316f565b92508282019050808211156139805761397f613926565b5b92915050565b5f613990826135ca565b915061399b836135ca565b9250828201905063ffffffff8111156139b7576139b6613926565b5b92915050565b7f4e6f206163746976652067616d650000000000000000000000000000000000005f82015250565b5f6139f1600e836138ae565b91506139fc826139bd565b602082019050919050565b5f6020820190508181035f830152613a1e816139e5565b9050919050565b7f4e6f2070656e64696e67207265717565737420746f207265636f7665720000005f82015250565b5f613a59601d836138ae565b9150613a6482613a25565b602082019050919050565b5f6020820190508181035f830152613a8681613a4d565b9050919050565b7f52657175657374206a7573742066756c66696c6c65642c206c657420565246205f8201527f636f6d706c657465000000000000000000000000000000000000000000000000602082015250565b5f613ae76028836138ae565b9150613af282613a8d565b604082019050919050565b5f6020820190508181035f830152613b1481613adb565b9050919050565b7f47616d65206e6f7420656c696769626c6520666f72207265636f7665727920795f8201527f6574000000000000000000000000000000000000000000000000000000000000602082015250565b5f613b756022836138ae565b9150613b8082613b1b565b604082019050919050565b5f6020820190508181035f830152613ba281613b69565b9050919050565b7f4e6f7468696e6720746f20726566756e640000000000000000000000000000005f82015250565b5f613bdd6011836138ae565b9150613be882613ba9565b602082019050919050565b5f6020820190508181035f830152613c0a81613bd1565b9050919050565b5f604082019050613c245f8301856131fd565b613c31602083018461348b565b9392505050565b5f604082019050613c4b5f83018561348b565b613c58602083018461348b565b9392505050565b7f47616d65206e6f7420656c696769626c6520666f7220666f7263652073746f705f8201527f2079657400000000000000000000000000000000000000000000000000000000602082015250565b5f613cb96024836138ae565b9150613cc482613c5f565b604082019050919050565b5f6020820190508181035f830152613ce681613cad565b9050919050565b5f613cf78261316f565b9150613d028361316f565b9250828203905081811115613d1a57613d19613926565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f613d578261316f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613d8957613d88613926565b5b600182019050919050565b5f613d9e826132ad565b9150613da9836132ad565b9250828203905060ff811115613dc257613dc1613926565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f613dff8261316f565b9150613e0a8361316f565b925082613e1a57613e19613dc8565b5b828206905092915050565b7f42657420616d6f756e742063616e6e6f74206265207a65726f000000000000005f82015250565b5f613e596019836138ae565b9150613e6482613e25565b602082019050919050565b5f6020820190508181035f830152613e8681613e4d565b9050919050565b7f42657420616d6f756e7420746f6f206c617267650000000000000000000000005f82015250565b5f613ec16014836138ae565b9150613ecc82613e8d565b602082019050919050565b5f6020820190508181035f830152613eee81613eb5565b9050919050565b7f496e76616c69642063686f73656e207369646520286d757374206265203120665f8201527f6f72204845414453206f72203220666f72205441494c53290000000000000000602082015250565b5f613f4f6038836138ae565b9150613f5a82613ef5565b604082019050919050565b5f6020820190508181035f830152613f7c81613f43565b9050919050565b7f557365722068617320616e206163746976652067616d650000000000000000005f82015250565b5f613fb76017836138ae565b9150613fc282613f83565b602082019050919050565b5f6020820190508181035f830152613fe481613fab565b9050919050565b7f557365722068617320612070656e64696e6720726571756573740000000000005f82015250565b5f61401f601a836138ae565b915061402a82613feb565b602082019050919050565b5f6020820190508181035f83015261404c81614013565b9050919050565b5f61405d8261316f565b91506140688361316f565b92508282026140768161316f565b9150828204841483151761408d5761408c613926565b5b5092915050565b5f61409e8261316f565b91506140a98361316f565b9250826140b9576140b8613dc8565b5b828204905092915050565b7f5061796f75742063616c63756c6174696f6e206f766572666c6f7700000000005f82015250565b5f6140f8601b836138ae565b9150614103826140c4565b602082019050919050565b5f6020820190508181035f830152614125816140ec565b9050919050565b5f8151905061413a81613178565b92915050565b5f6020828403121561415557614154613167565b5b5f6141628482850161412c565b91505092915050565b5f819050919050565b61417d8161416b565b82525050565b5f67ffffffffffffffff82169050919050565b61419f81614183565b82525050565b5f61ffff82169050919050565b6141bb816141a5565b82525050565b6141ca816135ca565b82525050565b5f6141ea6141e56141e0846132ad565b6136fc565b6135ca565b9050919050565b6141fa816141d0565b82525050565b5f60a0820190506142135f830188614174565b6142206020830187614196565b61422d60408301866141b2565b61423a60608301856141c1565b61424760808301846141f1565b9695505050505050565b5f6060820190506142645f83018661348b565b61427160208301856132b9565b61427e604083018461348b565b949350505050565b7f52657175657374206e6f7420666f756e640000000000000000000000000000005f82015250565b5f6142ba6011836138ae565b91506142c582614286565b602082019050919050565b5f6020820190508181035f8301526142e7816142ae565b9050919050565b7f5265717565737420616c72656164792066756c66696c6c6564000000000000005f82015250565b5f6143226019836138ae565b915061432d826142ee565b602082019050919050565b5f6020820190508181035f83015261434f81614316565b9050919050565b7f496e76616c69642072616e646f6d20776f726473206c656e67746800000000005f82015250565b5f61438a601b836138ae565b915061439582614356565b602082019050919050565b5f6020820190508181035f8301526143b78161437e565b9050919050565b7f52657175657374204944206d69736d61746368000000000000000000000000005f82015250565b5f6143f26013836138ae565b91506143fd826143be565b602082019050919050565b5f6020820190508181035f83015261441f816143e6565b9050919050565b7f42657420616d6f756e7420746f6f206c6172676520666f72207061796f7574205f8201527f63616c63756c6174696f6e000000000000000000000000000000000000000000602082015250565b5f614480602b836138ae565b915061448b82614426565b604082019050919050565b5f6020820190508181035f8301526144ad81614474565b9050919050565b5f6144be826132ad565b91506144c9836132ad565b9250828201905060ff8111156144e2576144e1613926565b5b92915050565b5f6144f2826132ad565b91506144fd836132ad565b92508261450d5761450c613dc8565b5b82820690509291505056fea26469706673582212203e818d623af3e238fd9d52509a69be771a4adb8114d3e2519f4dd447a4e7552364736f6c63430008140033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f0000000000000000000000000000000000000000000000000000000000000026c0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af00000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001
-----Decoded View---------------
Arg [0] : _gamaTokenAddress (address): 0x3a170C7C987F55C84f28733Bfa27962D8CdD5D3B
Arg [1] : vrfCoordinator (address): 0x943649947D45F06eBed0E6180C7eF04f60AB6c5f
Arg [2] : subscriptionId (uint64): 38
Arg [3] : keyHash (bytes32): 0xc0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af
Arg [4] : _callbackGasLimit (uint32): 2000000
Arg [5] : _requestConfirmations (uint16): 3
Arg [6] : _numWords (uint8): 1
-----Encoded View---------------
7 Constructor Arguments found :
Arg [0] : 0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b
Arg [1] : 000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000026
Arg [3] : c0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af
Arg [4] : 00000000000000000000000000000000000000000000000000000000001e8480
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000003
Arg [6] : 0000000000000000000000000000000000000000000000000000000000000001
Deployed Bytecode Sourcemap
89095:23066:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;107295:132;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;107581:204;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90169:31;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;54952:261;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;90338:60;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90630:48;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;105052:80;;;:::i;:::-;;90405:65;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90207:31;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;108317:2056;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;90685:44;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90987:33;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90910:31;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;6289:86;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;107934:203;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;9662:103;;;:::i;:::-;;104907:76;;;:::i;:::-;;8987:87;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;91483:51;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;99831:2549;;;:::i;:::-;;102499:2340;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;90288:43;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;105867:1277;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90788:33;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90948:32;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;9920:220;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;93406:2967;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;105328:381;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;107295:132;107366:7;107393:15;:26;107409:9;107393:26;;;;;;;;;;;;;;;;;;;;;107386:33;;107295:132;;;:::o;107581:204::-;107647:4;107664:21;107688:8;:16;107697:6;107688:16;;;;;;;;;;;;;;;107664:40;;107722:4;:16;;:25;;;;;;;;;;;;:55;;;;;107776:1;107751:4;:21;;;:26;;107722:55;107715:62;;;107581:204;;;:::o;90169:31::-;90199:1;90169:31;:::o;54952:261::-;55066:14;55052:28;;:10;:28;;;55048:111;;55124:10;55136:14;55098:53;;;;;;;;;;;;:::i;:::-;;;;;;;;55048:111;55165:42;55184:9;55195:11;55165:18;:42::i;:::-;54952:261;;:::o;90338:60::-;90379:19;90338:60;:::o;90630:48::-;90675:3;90630:48;:::o;105052:80::-;8873:13;:11;:13::i;:::-;2589:21:::1;:19;:21::i;:::-;105114:10:::2;:8;:10::i;:::-;2633:20:::1;:18;:20::i;:::-;105052:80::o:0;90405:65::-;90451:19;90405:65;:::o;90207:31::-;90237:1;90207:31;:::o;108317:2056::-;108389:13;108413:10;108434:16;108461;108488:14;108513:12;108536:14;108561:17;108589:18;108618:21;108650;108682:25;108748:1;108730:20;;:6;:20;;;108726:79;;108759:46;;;;;;;;;;:::i;:::-;;;;;;;;108726:79;108826:21;108850:8;:16;108859:6;108850:16;;;;;;;;;;;;;;;108826:40;;108898:4;:16;;:25;;;;;;;;;;;;108887:36;;108948:4;:16;;:26;;;;;;;;;;;;108934:40;;108998:4;:16;;:27;;;;;;;;;;;;108985:40;;109045:4;:16;;:23;;;109036:32;;109088:4;:16;;:23;;;;;;;;;;;;109079:32;;109131:4;:16;;:23;;;109122:32;;109177:4;:21;;;109165:33;;109229:4;:24;;;;;;;;;;;;109209:44;;;;109365:1;109356:6;:10;:50;;;;;90199:1;109371:15;;:6;:15;;;:34;;;;90237:1;109390:15;;:6;:15;;;109371:34;109356:50;109348:58;;109443:5;109427:21;;109478:5;109459:24;;109569:1;109556:9;:14;109552:196;;109587:29;109619:10;:21;109630:9;109619:21;;;;;;;;;;;109587:53;;109671:7;:14;;;;;;;;;;;;109655:30;;109719:7;:17;;;;;;;;;;;;109700:36;;109572:176;109552:196;109830:5;109811:24;;109850:8;109846:520;;;109943:28;90584:3;109989:4;:20;;;:38;;;;:::i;:::-;109974:12;:53;109943:84;;110042:21;90527:7;110084:4;:24;;;;;;;;;;;;:39;;;;:::i;:::-;110066:57;;:15;:57;110042:81;;110138:18;110172:1;110159:9;:14;;:31;;;;;110177:13;110159:31;110138:52;;110294:23;:43;;;;;110321:16;110294:43;:60;;;;;110341:13;110294:60;110275:79;;109860:506;;;109846:520;108715:1658;108317:2056;;;;;;;;;;;;;:::o;90685:44::-;90726:3;90685:44;:::o;90987:33::-;;;;:::o;90910:31::-;;;;:::o;6289:86::-;6336:4;6360:7;;;;;;;;;;;6353:14;;6289:86;:::o;107934:203::-;107998:4;108015:21;108039:8;:16;108048:6;108039:16;;;;;;;;;;;;;;;108015:40;;108074:4;:16;;:25;;;;;;;;;;;;108073:26;:56;;;;;108128:1;108103:4;:21;;;:26;108073:56;108066:63;;;107934:203;;;:::o;9662:103::-;8873:13;:11;:13::i;:::-;9727:30:::1;9754:1;9727:18;:30::i;:::-;9662:103::o:0;104907:76::-;8873:13;:11;:13::i;:::-;2589:21:::1;:19;:21::i;:::-;104967:8:::2;:6;:8::i;:::-;2633:20:::1;:18;:20::i;:::-;104907:76::o:0;8987:87::-;9033:7;9060:6;;;;;;;;;;9053:13;;8987:87;:::o;91483:51::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;99831:2549::-;2589:21;:19;:21::i;:::-;5894:19:::1;:17;:19::i;:::-;99909:21:::2;99933:8;:20;99942:10;99933:20;;;;;;;;;;;;;;;99909:44;;100054:4;:16;;:25;;;;;;;;;;;;100049:66;;100088:27;;;;;;;;;;:::i;:::-;;;;;;;;100049:66;100136:17;100156:4;:21;;;100136:41;;100269:1;100256:9;:14:::0;100252:96:::2;;100294:42;;;;;;;;;;:::i;:::-;;;;;;;;100252:96;100425:10;:21;100436:9;100425:21;;;;;;;;;;;:31;;;;;;;;;;;;:92;;;;;100514:2;100491:4;:20;;;:25;;;;:::i;:::-;100475:12;:41;;100425:92;100421:185;;;100541:53;;;;;;;;;;:::i;:::-;;;;;;;;100421:185;100688:28;90584:3;100734:4;:20;;;:38;;;;:::i;:::-;100719:12;:53;100688:84;;100783:21;90527:7;100825:4;:24;;;;;;;;;;;;:39;;;;:::i;:::-;100807:57;;:15;:57;100783:81;;100969:18;101003:1:::0;100990:9:::2;:14;;:46;;;;;101008:10;:21;101019:9;101008:21;;;;;;;;;;;:28;;;;;;;;;;;;100990:46;100969:67;;101117:23;101116:24;:45;;;;101145:16;101144:17;101116:45;:63;;;;101166:13;101165:14;101116:63;101112:150;;;101203:47;;;;;;;;;;:::i;:::-;;;;;;;;101112:150;101345:20;101368:4;:16;;:23;;;101345:46;;101432:1;101416:12;:17:::0;101412:60:::2;;101442:30;;;;;;;;;;:::i;:::-;;;;;;;;101412:60;101534:10;:21;101545:9;101534:21;;;;;;;;;;;;101527:28:::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;101573:15;:26;101589:9;101573:26;;;;;;;;;;;;101566:33;;;;;;;;;;;101617:16;:27;101634:9;101617:27;;;;;;;;;;;;101610:34;;;;;;;;;;;101724:4;101695;:16;;:26;;;:33;;;;;;;;;;;;;;;;;;101767:5;101739:4;:16;;:25;;;:33;;;;;;;;;;;;;;;;;;90726:3;101783:4;:16;;:23;;;:42;;;;;;;;;;;;;;;;;;101862:12;101836:4;:16;;:23;;:38;;;;101919:1;101895:4;:21;;:25;;;;101955:5;101931:4;:21;;;:29;;;;;;;;;;;;;;;;;;102036:9;:14;;;102051:10;102063:12;102036:40;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;102120:175;102153:4;102172;:16;;:27;;;;;;;;;;;;90726:3;102245:12;102272;102120:18;:175::i;:::-;102336:10;102322:50;;;102348:9;102359:12;102322:50;;;;;;;:::i;:::-;;;;;;;;99898:2482;;;;;;2633:20:::0;:18;:20::i;:::-;99831:2549::o;102499:2340::-;8873:13;:11;:13::i;:::-;2589:21:::1;:19;:21::i;:::-;102581::::2;102605:8;:16;102614:6;102605:16;;;;;;;;;;;;;;;102581:40;;102678:4;:16;;:25;;;;;;;;;;;;102673:66;;102712:27;;;;;;;;;;:::i;:::-;;;;;;;;102673:66;102752:17;102772:4;:21;;;102752:41;;102884:1;102871:9;:14;;:49;;;;;102889:10;:21;102900:9;102889:21;;;;;;;;;;;:31;;;;;;;;;;;;102871:49;:110;;;;;102978:2;102955:4;:20;;;:25;;;;:::i;:::-;102939:12;:41;;102871:110;102867:203;;;103005:53;;;;;;;;;;:::i;:::-;;;;;;;;102867:203;103144:28;90584:3;103190:4;:20;;;:38;;;;:::i;:::-;103175:12;:53;103144:84;;103239:21;90527:7;103281:4;:24;;;;;;;;;;;;:39;;;;:::i;:::-;103263:57;;:15;:57;103239:81;;103425:18;103459:1:::0;103446:9:::2;:14;;:46;;;;;103464:10;:21;103475:9;103464:21;;;;;;;;;;;:28;;;;;;;;;;;;103446:46;103425:67;;103573:23;103572:24;:45;;;;103601:16;103600:17;103572:45;:63;;;;103622:13;103621:14;103572:63;103568:152;;;103659:49;;;;;;;;;;:::i;:::-;;;;;;;;103568:152;103732:20;103755:4;:16;;:23;;;103732:46;;103819:1;103803:12;:17:::0;103799:60:::2;;103829:30;;;;;;;;;;:::i;:::-;;;;;;;;103799:60;103955:1;103942:9;:14;103938:172;;103980:15;:26;103996:9;103980:26;;;;;;;;;;;;103973:33;;;;;;;;;;;104028:16;:27;104045:9;104028:27;;;;;;;;;;;;104021:34;;;;;;;;;;;104077:10;:21;104088:9;104077:21;;;;;;;;;;;;104070:28:::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;103938:172;104186:4;104157;:16;;:26;;;:33;;;;;;;;;;;;;;;;;;104229:5;104201:4;:16;;:25;;;:33;;;;;;;;;;;;;;;;;;90675:3;104245:4;:16;;:23;;;:46;;;;;;;;;;;;;;;;;;104328:12;104302:4;:16;;:23;;:38;;;;104375:1;104351:4;:21;;:25;;;;104411:5;104387:4;:21;;;:29;;;;;;;;;;;;;;;;;;104492:9;:14;;;104507:6;104515:12;104492:36;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;104578:179;104611:4;104630;:16;;:27;;;;;;;;;;;;90675:3;104707:12;104734;104578:18;:179::i;:::-;104799:6;104785:46;;;104807:9;104818:12;104785:46;;;;;;;:::i;:::-;;;;;;;;102570:2269;;;;;;2633:20:::1;:18;:20::i;:::-;102499:2340:::0;:::o;90288:43::-;90329:2;90288:43;:::o;105867:1277::-;105929:19;105983:1;105965:20;;:6;:20;;;105961:79;;105994:46;;;;;;;;;;:::i;:::-;;;;;;;;105961:79;106061:21;106085:8;:16;106094:6;106085:16;;;;;;;;;;;;;;;106061:40;;106112:14;106129:4;:15;;:22;;;;106112:39;;106186:1;106176:6;:11;106172:43;;106213:1;106196:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;106189:26;;;;;;106172:43;106284:20;90329:2;106307:25;;:6;:25;:53;;106354:6;106307:53;;;90329:2;106307:53;;;106284:76;;106371:31;106422:12;106405:30;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;106371:64;;90329:2;106497:25;;:6;:25;106493:605;;;106601:9;106596:120;106620:6;106616:1;:10;106596:120;;;106669:4;:15;;106698:1;106694;106685:6;:10;;;;:::i;:::-;:14;;;;:::i;:::-;106669:31;;;;;;;;:::i;:::-;;;;;;;;;;;;106652:48;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;106664:1;106652:14;;;;;;;;:::i;:::-;;;;;;;:48;;;;106628:3;;;;;:::i;:::-;;;;106596:120;;;;106493:605;;;106796:19;106839:1;106818:4;:17;;;;;;;;;;;;:22;;;:69;;106886:1;106866:4;:17;;;;;;;;;;;;:21;;;;:::i;:::-;106818:69;;;106862:1;90329:2;106843:20;;;;:::i;:::-;106818:69;106796:91;;;;106921:9;106916:171;90329:2;106936:20;;:1;:20;106916:171;;;106999:4;:15;;90329:2;107015:55;;107049:1;90329:2;107016:30;;:11;:30;;;;:::i;:::-;:34;;;;:::i;:::-;107015:55;;;;:::i;:::-;106999:72;;;;;;;;:::i;:::-;;;;;;;;;;;;106982:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;106994:1;106982:14;;;;;;;;:::i;:::-;;;;;;;:89;;;;106958:3;;;;;:::i;:::-;;;;106916:171;;;;106733:365;106493:605;107125:11;107118:18;;;;;;105867:1277;;;;:::o;90788:33::-;;;:::o;90948:32::-;;;;:::o;9920:220::-;8873:13;:11;:13::i;:::-;10025:1:::1;10005:22;;:8;:22;;::::0;10001:93:::1;;10079:1;10051:31;;;;;;;;;;;:::i;:::-;;;;;;;;10001:93;10104:28;10123:8;10104:18;:28::i;:::-;9920:220:::0;:::o;93406:2967::-;93503:17;2589:21;:19;:21::i;:::-;5894:19:::1;:17;:19::i;:::-;93616:1:::2;93606:6;:11:::0;93602:73:::2;;93626:49;;;;;;;;;;:::i;:::-;;;;;;;;93602:73;90379:19;93690:6;:23;93686:80;;;93722:44;;;;;;;;;;:::i;:::-;;;;;;;;93686:80;90199:1;93781:19;;:10;:19;;;;:42;;;;;90237:1;93804:19;;:10;:19;;;;93781:42;93777:135;;;93832:80;;;;;;;;;;:::i;:::-;;;;;;;;93777:135;93973:21;93997:8;:20;94006:10;93997:20;;;;;;;;;;;;;;;93973:44;;94032:4;:16;;:25;;;;;;;;;;;;94028:74;;;94066:36;;;;;;;;;;:::i;:::-;;;;;;;;94028:74;94142:1;94117:4;:21;;;:26;94113:78;;94152:39;;;;;;;;;;:::i;:::-;;;;;;;;94113:78;94238:47;94266:10;94278:6;94238:27;:47::i;:::-;94337:23;94372:1;94363:6;:10;;;;:::i;:::-;94337:36;;94411:6;94406:1;94388:15;:19;;;;:::i;:::-;:29;94384:95;;94426:53;;;;;;;;;;:::i;:::-;;;;;;;;94384:95;90451:19;94494:15;:37;94490:132;;;94573:15;90451:19;94555:55;;;;;;;;;;;;:::i;:::-;;;;;;;;94490:132;94724:25;94752:9;:30;;;:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;94724:60;;94817:17;94799:15;:35;94795:128;;;94876:15;94893:17;94858:53;;;;;;;;;;;;:::i;:::-;;;;;;;;94795:128;95000:9;:18;;;95019:10;95031:6;95000:38;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;95113:6;95091:18;;:28;;;;;;;:::i;:::-;;;;;;;;95191:11;:30;;;95236:9;95260:16;95291:20;95326:16;95357:8;95191:185;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;95179:197;;95447:128;;;;;;;;95558:5;95447:128;;;;;;95528:4;95447:128;;;;;;95503:1;95489:16;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;95447:128;;::::0;95423:10:::2;:21;95434:9;95423:21;;;;;;;;;;;:152;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;95662:10;95633:15;:26;95649:9;95633:26;;;;;;;;;;;;:39;;;;;;;;;;;;;;;;;;95713:4;95683:16;:27;95700:9;95683:27;;;;;;;;;;;;:34;;;;;;;;;;;;;;;;;;95818:15;95784:4;:24;;;:50;;;;;;;;;;;;;;;;;;95868:12;95845:4;:20;;:35;;;;95915:5;95891:4;:21;;;:29;;;;;;;;;;;;;;;;;;96000:196;;;;;;;;96035:4;96000:196;;;;;;96065:5;96000:196;;;;;;96097:10;96000:196;;;;;;96130:1;96000:196;;;;;;96154:6;96000:196;;;;96183:1;96000:196;;::::0;95981:4:::2;:16;;:215;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;96241:9;96217:4;:21;;:33;;;;96286:10;96276:52;;;96298:9;96309:10;96321:6;96276:52;;;;;;;;:::i;:::-;;;;;;;;96349:16;;;2633:20:::0;:18;:20::i;:::-;93406:2967;;;;:::o;105328:381::-;105398:26;;:::i;:::-;105435:18;105494:1;105476:20;;:6;:20;;;105472:79;;105505:46;;;;;;;;;;:::i;:::-;;;;;;;;105472:79;105562:21;105586:8;:16;105595:6;105586:16;;;;;;;;;;;;;;;105562:40;;105635:4;:16;;105666:4;:24;;;;;;;;;;;;105613:88;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;105328:381;;;:::o;96548:3191::-;2589:21;:19;:21::i;:::-;96734:29:::1;96766:10;:21;96777:9;96766:21;;;;;;;;;;;96734:53;;96803:7;:14;;;;;;;;;;;;96798:57;;96826:29;;;;;;;;;;:::i;:::-;;;;;;;;96798:57;96870:7;:17;;;;;;;;;;;;96866:67;;;96896:37;;;;;;;;;;:::i;:::-;;;;;;;;96866:67;96970:8;96948:30;;:11;:18;:30;96944:82;;96987:39;;;;;;;;;;:::i;:::-;;;;;;;;96944:82;97085:14;97102:15;:26;97118:9;97102:26;;;;;;;;;;;;;;;;;;;;;97085:43;;97161:1;97143:20;;:6;:20;;::::0;97139:67:::1;;97172:34;;;;;;;;;;:::i;:::-;;;;;;;;97139:67;97227:21;97251:8;:16;97260:6;97251:16;;;;;;;;;;;;;;;97227:40;;97307:9;97282:4;:21;;;:34;97278:79;;97325:32;;;;;;;;;;:::i;:::-;;;;;;;;97278:79;97463:4;97443:7;:17;;;:24;;;;;;;;;;;;;;;;;;97500:11;97478:7;:19;;:33;;;;;;;;;;;;:::i;:::-;;97546:4;97522;:21;;;:28;;;;;;;;;;;;;;;;;;97618:4;:16;;:25;;;;;;;;;;;;97613:372;;97750:10;:21;97761:9;97750:21;;;;;;;;;;;;97743:28:::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;97793:15;:26;97809:9;97793:26;;;;;;;;;;;;97786:33;;;;;;;;;;;97841:16;:27;97858:9;97841:27;;;;;;;;;;;;97834:34;;;;;;;;;;;97907:1;97883:4;:21;;:25;;;;97947:5;97923:4;:21;;;:29;;;;;;;;;;;;;;;;;;97967:7;;;;;97613:372;98032:16;98051:4;:16;;:27;;;;;;;;;;;;98032:46;;98089:17;98109:4;:16;;:23;;;98089:43;;98236:12;98288:1;90280;98258:26;;:11;98270:1;98258:14;;;;;;;;:::i;:::-;;;;;;;;:26;;;;:::i;:::-;98257:32;;;;:::i;:::-;98236:54;;98343:14;98390:6;98376:20;;:10;:20;;::::0;98372:282:::1;;98492:1;98472:17;:21;;;;:::i;:::-;98460:9;:33;98456:150;;;98521:69;;;;;;;;;;:::i;:::-;;;;;;;;98456:150;98641:1;98629:9;:13;;;;:::i;:::-;98620:22;;98372:282;98725:6;98699:4;:16;;:23;;;:32;;;;;;;;;;;;;;;;;;98770:5;98742:4;:16;;:25;;;:33;;;;;;;;;;;;;;;;;;98815:4;98786;:16;;:26;;;:33;;;;;;;;;;;;;;;;;;98856:6;98830:4;:16;;:23;;:32;;;;98910:139;98943:4;98962:10;98987:6;99008:9;99032:6;98910:18;:139::i;:::-;99121:1;99112:6;:10;99108:70;;;99160:6;99139:17;;:27;;;;;;;:::i;:::-;;;;;;;;99108:70;99258:16;;99256:18;;;;;;;;;;;99316:15;:26;99332:9;99316:26;;;;;;;;;;;;99309:33;;;;;;;;;;;99360:16;:27;99377:9;99360:27;;;;;;;;;;;;99353:34;;;;;;;;;;;99405:10;:21;99416:9;99405:21;;;;;;;;;;;;99398:28:::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;99461:1;99437:4;:21;;:25;;;;99497:5;99473:4;:21;;;:29;;;;;;;;;;;;;;;;;;99606:1;99597:6;:10;99593:73;;;99624:9;:14;;;99639:6;99647;99624:30;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;99593:73;99697:6;99683:48;;;99705:9;99716:6;99724;99683:48;;;;;;;;:::i;:::-;;;;;;;;96656:3083;;;;;;;2621:1;2633:20:::0;:18;:20::i;:::-;96548:3191;;:::o;9152:166::-;9223:12;:10;:12::i;:::-;9212:23;;:7;:5;:7::i;:::-;:23;;;9208:103;;9286:12;:10;:12::i;:::-;9259:40;;;;;;;;;;;:::i;:::-;;;;;;;;9208:103;9152:166::o;2669:315::-;1967:1;2798:7;;:18;2794:88;;2840:30;;;;;;;;;;;;;;2794:88;1967:1;2959:7;:17;;;;2669:315::o;7190:120::-;6153:16;:14;:16::i;:::-;7259:5:::1;7249:7;;:15;;;;;;;;;;;;;;;;;;7280:22;7289:12;:10;:12::i;:::-;7280:22;;;;;;:::i;:::-;;;;;;;;7190:120::o:0;2992:212::-;1924:1;3175:7;:21;;;;2992:212::o;10300:191::-;10374:16;10393:6;;;;;;;;;;10374:25;;10419:8;10410:6;;:17;;;;;;;;;;;;;;;;;;10474:8;10443:40;;10464:8;10443:40;;;;;;;;;;;;10363:128;10300:191;:::o;6931:118::-;5894:19;:17;:19::i;:::-;7001:4:::1;6991:7:::0;::::1;:14;;;;;;;;;;;;;;;;;;7021:20;7028:12;:10;:12::i;:::-;7021:20;;;;;;:::i;:::-;;;;;;;;6931:118::o:0;6448:132::-;6514:8;:6;:8::i;:::-;6510:63;;;6546:15;;;;;;;;;;;;;;6510:63;6448:132::o;111290:868::-;111476:24;111503:203;;;;;;;;111541:10;111503:203;;;;;;111581:6;111503:203;;;;;;111649:15;111503:203;;;;;;111610:6;111503:203;;;;111688:6;111503:203;;;111476:230;;90329:2;111723:41;;:4;:15;;:22;;;;:41;111719:432;;;111824:4;:15;;111845:6;111824:28;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;90329:2;111893:41;;:4;:15;;:22;;;;:41;;;;:::i;:::-;111867:4;:17;;;:68;;;;;;;;;;;;;;;;;;111719:432;;;112056:6;112019:4;:15;;112035:4;:17;;;;;;;;;;;;112019:34;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:43;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;90329:2;112118:1;112098:4;:17;;;;;;;;;;;;:21;;;;:::i;:::-;112097:42;;;;:::i;:::-;112077:4;:17;;;:62;;;;;;;;;;;;;;;;;;111719:432;111465:693;111290:868;;;;;:::o;110583:414::-;110710:6;110680:9;:19;;;110700:6;110680:27;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:36;110676:136;;;110764:6;110772:9;:19;;;110792:6;110772:27;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;110740:60;;;;;;;;;;;;:::i;:::-;;;;;;;;110676:136;110873:6;110828:9;:19;;;110848:6;110864:4;110828:42;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:51;110824:164;;;110925:6;110933:9;:19;;;110953:6;110969:4;110933:42;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;110903:73;;;;;;;;;;;;:::i;:::-;;;;;;;;110824:164;110583:414;;:::o;4199:98::-;4252:7;4279:10;4272:17;;4199:98;:::o;6657:130::-;6721:8;:6;:8::i;:::-;6716:64;;6753:15;;;;;;;;;;;;;;6716:64;6657:130::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::o;7:75:1:-;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;211:117;320:1;317;310:12;334:77;371:7;400:5;389:16;;334:77;;;:::o;417:122::-;490:24;508:5;490:24;:::i;:::-;483:5;480:35;470:63;;529:1;526;519:12;470:63;417:122;:::o;545:139::-;591:5;629:6;616:20;607:29;;645:33;672:5;645:33;:::i;:::-;545:139;;;;:::o;690:329::-;749:6;798:2;786:9;777:7;773:23;769:32;766:119;;;804:79;;:::i;:::-;766:119;924:1;949:53;994:7;985:6;974:9;970:22;949:53;:::i;:::-;939:63;;895:117;690:329;;;;:::o;1025:126::-;1062:7;1102:42;1095:5;1091:54;1080:65;;1025:126;;;:::o;1157:96::-;1194:7;1223:24;1241:5;1223:24;:::i;:::-;1212:35;;1157:96;;;:::o;1259:118::-;1346:24;1364:5;1346:24;:::i;:::-;1341:3;1334:37;1259:118;;:::o;1383:222::-;1476:4;1514:2;1503:9;1499:18;1491:26;;1527:71;1595:1;1584:9;1580:17;1571:6;1527:71;:::i;:::-;1383:222;;;;:::o;1611:122::-;1684:24;1702:5;1684:24;:::i;:::-;1677:5;1674:35;1664:63;;1723:1;1720;1713:12;1664:63;1611:122;:::o;1739:139::-;1785:5;1823:6;1810:20;1801:29;;1839:33;1866:5;1839:33;:::i;:::-;1739:139;;;;:::o;1884:329::-;1943:6;1992:2;1980:9;1971:7;1967:23;1963:32;1960:119;;;1998:79;;:::i;:::-;1960:119;2118:1;2143:53;2188:7;2179:6;2168:9;2164:22;2143:53;:::i;:::-;2133:63;;2089:117;1884:329;;;;:::o;2219:90::-;2253:7;2296:5;2289:13;2282:21;2271:32;;2219:90;;;:::o;2315:109::-;2396:21;2411:5;2396:21;:::i;:::-;2391:3;2384:34;2315:109;;:::o;2430:210::-;2517:4;2555:2;2544:9;2540:18;2532:26;;2568:65;2630:1;2619:9;2615:17;2606:6;2568:65;:::i;:::-;2430:210;;;;:::o;2646:86::-;2681:7;2721:4;2714:5;2710:16;2699:27;;2646:86;;;:::o;2738:112::-;2821:22;2837:5;2821:22;:::i;:::-;2816:3;2809:35;2738:112;;:::o;2856:214::-;2945:4;2983:2;2972:9;2968:18;2960:26;;2996:67;3060:1;3049:9;3045:17;3036:6;2996:67;:::i;:::-;2856:214;;;;:::o;3076:117::-;3185:1;3182;3175:12;3199:102;3240:6;3291:2;3287:7;3282:2;3275:5;3271:14;3267:28;3257:38;;3199:102;;;:::o;3307:180::-;3355:77;3352:1;3345:88;3452:4;3449:1;3442:15;3476:4;3473:1;3466:15;3493:281;3576:27;3598:4;3576:27;:::i;:::-;3568:6;3564:40;3706:6;3694:10;3691:22;3670:18;3658:10;3655:34;3652:62;3649:88;;;3717:18;;:::i;:::-;3649:88;3757:10;3753:2;3746:22;3536:238;3493:281;;:::o;3780:129::-;3814:6;3841:20;;:::i;:::-;3831:30;;3870:33;3898:4;3890:6;3870:33;:::i;:::-;3780:129;;;:::o;3915:311::-;3992:4;4082:18;4074:6;4071:30;4068:56;;;4104:18;;:::i;:::-;4068:56;4154:4;4146:6;4142:17;4134:25;;4214:4;4208;4204:15;4196:23;;3915:311;;;:::o;4232:117::-;4341:1;4338;4331:12;4372:710;4468:5;4493:81;4509:64;4566:6;4509:64;:::i;:::-;4493:81;:::i;:::-;4484:90;;4594:5;4623:6;4616:5;4609:21;4657:4;4650:5;4646:16;4639:23;;4710:4;4702:6;4698:17;4690:6;4686:30;4739:3;4731:6;4728:15;4725:122;;;4758:79;;:::i;:::-;4725:122;4873:6;4856:220;4890:6;4885:3;4882:15;4856:220;;;4965:3;4994:37;5027:3;5015:10;4994:37;:::i;:::-;4989:3;4982:50;5061:4;5056:3;5052:14;5045:21;;4932:144;4916:4;4911:3;4907:14;4900:21;;4856:220;;;4860:21;4474:608;;4372:710;;;;;:::o;5105:370::-;5176:5;5225:3;5218:4;5210:6;5206:17;5202:27;5192:122;;5233:79;;:::i;:::-;5192:122;5350:6;5337:20;5375:94;5465:3;5457:6;5450:4;5442:6;5438:17;5375:94;:::i;:::-;5366:103;;5182:293;5105:370;;;;:::o;5481:684::-;5574:6;5582;5631:2;5619:9;5610:7;5606:23;5602:32;5599:119;;;5637:79;;:::i;:::-;5599:119;5757:1;5782:53;5827:7;5818:6;5807:9;5803:22;5782:53;:::i;:::-;5772:63;;5728:117;5912:2;5901:9;5897:18;5884:32;5943:18;5935:6;5932:30;5929:117;;;5965:79;;:::i;:::-;5929:117;6070:78;6140:7;6131:6;6120:9;6116:22;6070:78;:::i;:::-;6060:88;;5855:303;5481:684;;;;;:::o;6171:118::-;6258:24;6276:5;6258:24;:::i;:::-;6253:3;6246:37;6171:118;;:::o;6295:222::-;6388:4;6426:2;6415:9;6411:18;6403:26;;6439:71;6507:1;6496:9;6492:17;6483:6;6439:71;:::i;:::-;6295:222;;;;:::o;6523:1357::-;6882:4;6920:3;6909:9;6905:19;6897:27;;6934:65;6996:1;6985:9;6981:17;6972:6;6934:65;:::i;:::-;7009:66;7071:2;7060:9;7056:18;7047:6;7009:66;:::i;:::-;7085;7147:2;7136:9;7132:18;7123:6;7085:66;:::i;:::-;7161:68;7225:2;7214:9;7210:18;7201:6;7161:68;:::i;:::-;7239:73;7307:3;7296:9;7292:19;7283:6;7239:73;:::i;:::-;7322:69;7386:3;7375:9;7371:19;7362:6;7322:69;:::i;:::-;7401:73;7469:3;7458:9;7454:19;7445:6;7401:73;:::i;:::-;7484;7552:3;7541:9;7537:19;7528:6;7484:73;:::i;:::-;7567:67;7629:3;7618:9;7614:19;7605:6;7567:67;:::i;:::-;7644;7706:3;7695:9;7691:19;7682:6;7644:67;:::i;:::-;7721:68;7784:3;7773:9;7769:19;7759:7;7721:68;:::i;:::-;7799:74;7868:3;7857:9;7853:19;7843:7;7799:74;:::i;:::-;6523:1357;;;;;;;;;;;;;;;:::o;7886:308::-;7995:4;8033:2;8022:9;8018:18;8010:26;;8046:65;8108:1;8097:9;8093:17;8084:6;8046:65;:::i;:::-;8121:66;8183:2;8172:9;8168:18;8159:6;8121:66;:::i;:::-;7886:308;;;;;:::o;8200:142::-;8295:6;8329:5;8323:12;8313:22;;8200:142;;;:::o;8348:212::-;8475:11;8509:6;8504:3;8497:19;8549:4;8544:3;8540:14;8525:29;;8348:212;;;;:::o;8566:160::-;8661:4;8684:3;8676:11;;8714:4;8709:3;8705:14;8697:22;;8566:160;;;:::o;8732:102::-;8805:22;8821:5;8805:22;:::i;:::-;8800:3;8793:35;8732:102;;:::o;8840:93::-;8876:7;8916:10;8909:5;8905:22;8894:33;;8840:93;;;:::o;8939:105::-;9014:23;9031:5;9014:23;:::i;:::-;9009:3;9002:36;8939:105;;:::o;9050:108::-;9127:24;9145:5;9127:24;:::i;:::-;9122:3;9115:37;9050:108;;:::o;9210:1039::-;9353:4;9348:3;9344:14;9446:4;9439:5;9435:16;9429:23;9465:59;9518:4;9513:3;9509:14;9495:12;9465:59;:::i;:::-;9368:166;9625:4;9618:5;9614:16;9608:23;9644:59;9697:4;9692:3;9688:14;9674:12;9644:59;:::i;:::-;9544:169;9800:4;9793:5;9789:16;9783:23;9819:61;9874:4;9869:3;9865:14;9851:12;9819:61;:::i;:::-;9723:167;9974:4;9967:5;9963:16;9957:23;9993:63;10050:4;10045:3;10041:14;10027:12;9993:63;:::i;:::-;9900:166;10150:4;10143:5;10139:16;10133:23;10169:63;10226:4;10221:3;10217:14;10203:12;10169:63;:::i;:::-;10076:166;9322:927;9210:1039;;:::o;10255:291::-;10380:10;10401:102;10499:3;10491:6;10401:102;:::i;:::-;10535:4;10530:3;10526:14;10512:28;;10255:291;;;;:::o;10552:141::-;10650:4;10682;10677:3;10673:14;10665:22;;10552:141;;;:::o;10749:956::-;10924:3;10953:82;11029:5;10953:82;:::i;:::-;11051:114;11158:6;11153:3;11051:114;:::i;:::-;11044:121;;11189:84;11267:5;11189:84;:::i;:::-;11296:7;11327:1;11312:368;11337:6;11334:1;11331:13;11312:368;;;11413:6;11407:13;11440:119;11555:3;11540:13;11440:119;:::i;:::-;11433:126;;11582:88;11663:6;11582:88;:::i;:::-;11572:98;;11372:308;11359:1;11356;11352:9;11347:14;;11312:368;;;11316:14;11696:3;11689:10;;10929:776;;;10749:956;;;;:::o;11711:485::-;11910:4;11948:2;11937:9;11933:18;11925:26;;11997:9;11991:4;11987:20;11983:1;11972:9;11968:17;11961:47;12025:164;12184:4;12175:6;12025:164;:::i;:::-;12017:172;;11711:485;;;;:::o;12202:60::-;12230:3;12251:5;12244:12;;12202:60;;;:::o;12268:142::-;12318:9;12351:53;12369:34;12378:24;12396:5;12378:24;:::i;:::-;12369:34;:::i;:::-;12351:53;:::i;:::-;12338:66;;12268:142;;;:::o;12416:126::-;12466:9;12499:37;12530:5;12499:37;:::i;:::-;12486:50;;12416:126;;;:::o;12548:141::-;12613:9;12646:37;12677:5;12646:37;:::i;:::-;12633:50;;12548:141;;;:::o;12695:161::-;12797:52;12843:5;12797:52;:::i;:::-;12792:3;12785:65;12695:161;;:::o;12862:252::-;12970:4;13008:2;12997:9;12993:18;12985:26;;13021:86;13104:1;13093:9;13089:17;13080:6;13021:86;:::i;:::-;12862:252;;;;:::o;13120:118::-;13191:22;13207:5;13191:22;:::i;:::-;13184:5;13181:33;13171:61;;13228:1;13225;13218:12;13171:61;13120:118;:::o;13244:135::-;13288:5;13326:6;13313:20;13304:29;;13342:31;13367:5;13342:31;:::i;:::-;13244:135;;;;:::o;13385:470::-;13451:6;13459;13508:2;13496:9;13487:7;13483:23;13479:32;13476:119;;;13514:79;;:::i;:::-;13476:119;13634:1;13659:51;13702:7;13693:6;13682:9;13678:22;13659:51;:::i;:::-;13649:61;;13605:115;13759:2;13785:53;13830:7;13821:6;13810:9;13806:22;13785:53;:::i;:::-;13775:63;;13730:118;13385:470;;;;;:::o;13861:99::-;13932:21;13947:5;13932:21;:::i;:::-;13927:3;13920:34;13861:99;;:::o;14010:1208::-;14161:4;14156:3;14152:14;14252:4;14245:5;14241:16;14235:23;14271:57;14322:4;14317:3;14313:14;14299:12;14271:57;:::i;:::-;14176:162;14425:4;14418:5;14414:16;14408:23;14444:57;14495:4;14490:3;14486:14;14472:12;14444:57;:::i;:::-;14348:163;14599:4;14592:5;14588:16;14582:23;14618:59;14671:4;14666:3;14662:14;14648:12;14618:59;:::i;:::-;14521:166;14771:4;14764:5;14760:16;14754:23;14790:59;14843:4;14838:3;14834:14;14820:12;14790:59;:::i;:::-;14697:162;14943:4;14936:5;14932:16;14926:23;14962:63;15019:4;15014:3;15010:14;14996:12;14962:63;:::i;:::-;14869:166;15119:4;15112:5;15108:16;15102:23;15138:63;15195:4;15190:3;15186:14;15172:12;15138:63;:::i;:::-;15045:166;14130:1088;14010:1208;;:::o;15224:442::-;15399:4;15437:3;15426:9;15422:19;15414:27;;15451:125;15573:1;15562:9;15558:17;15549:6;15451:125;:::i;:::-;15586:73;15654:3;15643:9;15639:19;15630:6;15586:73;:::i;:::-;15224:442;;;;;:::o;15672:332::-;15793:4;15831:2;15820:9;15816:18;15808:26;;15844:71;15912:1;15901:9;15897:17;15888:6;15844:71;:::i;:::-;15925:72;15993:2;15982:9;15978:18;15969:6;15925:72;:::i;:::-;15672:332;;;;;:::o;16010:169::-;16094:11;16128:6;16123:3;16116:19;16168:4;16163:3;16159:14;16144:29;;16010:169;;;;:::o;16185:172::-;16325:24;16321:1;16313:6;16309:14;16302:48;16185:172;:::o;16363:366::-;16505:3;16526:67;16590:2;16585:3;16526:67;:::i;:::-;16519:74;;16602:93;16691:3;16602:93;:::i;:::-;16720:2;16715:3;16711:12;16704:19;;16363:366;;;:::o;16735:419::-;16901:4;16939:2;16928:9;16924:18;16916:26;;16988:9;16982:4;16978:20;16974:1;16963:9;16959:17;16952:47;17016:131;17142:4;17016:131;:::i;:::-;17008:139;;16735:419;;;:::o;17160:180::-;17208:77;17205:1;17198:88;17305:4;17302:1;17295:15;17329:4;17326:1;17319:15;17346:191;17386:3;17405:20;17423:1;17405:20;:::i;:::-;17400:25;;17439:20;17457:1;17439:20;:::i;:::-;17434:25;;17482:1;17479;17475:9;17468:16;;17503:3;17500:1;17497:10;17494:36;;;17510:18;;:::i;:::-;17494:36;17346:191;;;;:::o;17543:197::-;17582:3;17601:19;17618:1;17601:19;:::i;:::-;17596:24;;17634:19;17651:1;17634:19;:::i;:::-;17629:24;;17676:1;17673;17669:9;17662:16;;17699:10;17694:3;17691:19;17688:45;;;17713:18;;:::i;:::-;17688:45;17543:197;;;;:::o;17746:164::-;17886:16;17882:1;17874:6;17870:14;17863:40;17746:164;:::o;17916:366::-;18058:3;18079:67;18143:2;18138:3;18079:67;:::i;:::-;18072:74;;18155:93;18244:3;18155:93;:::i;:::-;18273:2;18268:3;18264:12;18257:19;;17916:366;;;:::o;18288:419::-;18454:4;18492:2;18481:9;18477:18;18469:26;;18541:9;18535:4;18531:20;18527:1;18516:9;18512:17;18505:47;18569:131;18695:4;18569:131;:::i;:::-;18561:139;;18288:419;;;:::o;18713:179::-;18853:31;18849:1;18841:6;18837:14;18830:55;18713:179;:::o;18898:366::-;19040:3;19061:67;19125:2;19120:3;19061:67;:::i;:::-;19054:74;;19137:93;19226:3;19137:93;:::i;:::-;19255:2;19250:3;19246:12;19239:19;;18898:366;;;:::o;19270:419::-;19436:4;19474:2;19463:9;19459:18;19451:26;;19523:9;19517:4;19513:20;19509:1;19498:9;19494:17;19487:47;19551:131;19677:4;19551:131;:::i;:::-;19543:139;;19270:419;;;:::o;19695:227::-;19835:34;19831:1;19823:6;19819:14;19812:58;19904:10;19899:2;19891:6;19887:15;19880:35;19695:227;:::o;19928:366::-;20070:3;20091:67;20155:2;20150:3;20091:67;:::i;:::-;20084:74;;20167:93;20256:3;20167:93;:::i;:::-;20285:2;20280:3;20276:12;20269:19;;19928:366;;;:::o;20300:419::-;20466:4;20504:2;20493:9;20489:18;20481:26;;20553:9;20547:4;20543:20;20539:1;20528:9;20524:17;20517:47;20581:131;20707:4;20581:131;:::i;:::-;20573:139;;20300:419;;;:::o;20725:221::-;20865:34;20861:1;20853:6;20849:14;20842:58;20934:4;20929:2;20921:6;20917:15;20910:29;20725:221;:::o;20952:366::-;21094:3;21115:67;21179:2;21174:3;21115:67;:::i;:::-;21108:74;;21191:93;21280:3;21191:93;:::i;:::-;21309:2;21304:3;21300:12;21293:19;;20952:366;;;:::o;21324:419::-;21490:4;21528:2;21517:9;21513:18;21505:26;;21577:9;21571:4;21567:20;21563:1;21552:9;21548:17;21541:47;21605:131;21731:4;21605:131;:::i;:::-;21597:139;;21324:419;;;:::o;21749:167::-;21889:19;21885:1;21877:6;21873:14;21866:43;21749:167;:::o;21922:366::-;22064:3;22085:67;22149:2;22144:3;22085:67;:::i;:::-;22078:74;;22161:93;22250:3;22161:93;:::i;:::-;22279:2;22274:3;22270:12;22263:19;;21922:366;;;:::o;22294:419::-;22460:4;22498:2;22487:9;22483:18;22475:26;;22547:9;22541:4;22537:20;22533:1;22522:9;22518:17;22511:47;22575:131;22701:4;22575:131;:::i;:::-;22567:139;;22294:419;;;:::o;22719:332::-;22840:4;22878:2;22867:9;22863:18;22855:26;;22891:71;22959:1;22948:9;22944:17;22935:6;22891:71;:::i;:::-;22972:72;23040:2;23029:9;23025:18;23016:6;22972:72;:::i;:::-;22719:332;;;;;:::o;23057:::-;23178:4;23216:2;23205:9;23201:18;23193:26;;23229:71;23297:1;23286:9;23282:17;23273:6;23229:71;:::i;:::-;23310:72;23378:2;23367:9;23363:18;23354:6;23310:72;:::i;:::-;23057:332;;;;;:::o;23395:223::-;23535:34;23531:1;23523:6;23519:14;23512:58;23604:6;23599:2;23591:6;23587:15;23580:31;23395:223;:::o;23624:366::-;23766:3;23787:67;23851:2;23846:3;23787:67;:::i;:::-;23780:74;;23863:93;23952:3;23863:93;:::i;:::-;23981:2;23976:3;23972:12;23965:19;;23624:366;;;:::o;23996:419::-;24162:4;24200:2;24189:9;24185:18;24177:26;;24249:9;24243:4;24239:20;24235:1;24224:9;24220:17;24213:47;24277:131;24403:4;24277:131;:::i;:::-;24269:139;;23996:419;;;:::o;24421:194::-;24461:4;24481:20;24499:1;24481:20;:::i;:::-;24476:25;;24515:20;24533:1;24515:20;:::i;:::-;24510:25;;24559:1;24556;24552:9;24544:17;;24583:1;24577:4;24574:11;24571:37;;;24588:18;;:::i;:::-;24571:37;24421:194;;;;:::o;24621:180::-;24669:77;24666:1;24659:88;24766:4;24763:1;24756:15;24790:4;24787:1;24780:15;24807:233;24846:3;24869:24;24887:5;24869:24;:::i;:::-;24860:33;;24915:66;24908:5;24905:77;24902:103;;24985:18;;:::i;:::-;24902:103;25032:1;25025:5;25021:13;25014:20;;24807:233;;;:::o;25046:191::-;25084:4;25104:18;25120:1;25104:18;:::i;:::-;25099:23;;25136:18;25152:1;25136:18;:::i;:::-;25131:23;;25178:1;25175;25171:9;25163:17;;25202:4;25196;25193:14;25190:40;;;25210:18;;:::i;:::-;25190:40;25046:191;;;;:::o;25243:180::-;25291:77;25288:1;25281:88;25388:4;25385:1;25378:15;25412:4;25409:1;25402:15;25429:176;25461:1;25478:20;25496:1;25478:20;:::i;:::-;25473:25;;25512:20;25530:1;25512:20;:::i;:::-;25507:25;;25551:1;25541:35;;25556:18;;:::i;:::-;25541:35;25597:1;25594;25590:9;25585:14;;25429:176;;;;:::o;25611:175::-;25751:27;25747:1;25739:6;25735:14;25728:51;25611:175;:::o;25792:366::-;25934:3;25955:67;26019:2;26014:3;25955:67;:::i;:::-;25948:74;;26031:93;26120:3;26031:93;:::i;:::-;26149:2;26144:3;26140:12;26133:19;;25792:366;;;:::o;26164:419::-;26330:4;26368:2;26357:9;26353:18;26345:26;;26417:9;26411:4;26407:20;26403:1;26392:9;26388:17;26381:47;26445:131;26571:4;26445:131;:::i;:::-;26437:139;;26164:419;;;:::o;26589:170::-;26729:22;26725:1;26717:6;26713:14;26706:46;26589:170;:::o;26765:366::-;26907:3;26928:67;26992:2;26987:3;26928:67;:::i;:::-;26921:74;;27004:93;27093:3;27004:93;:::i;:::-;27122:2;27117:3;27113:12;27106:19;;26765:366;;;:::o;27137:419::-;27303:4;27341:2;27330:9;27326:18;27318:26;;27390:9;27384:4;27380:20;27376:1;27365:9;27361:17;27354:47;27418:131;27544:4;27418:131;:::i;:::-;27410:139;;27137:419;;;:::o;27562:243::-;27702:34;27698:1;27690:6;27686:14;27679:58;27771:26;27766:2;27758:6;27754:15;27747:51;27562:243;:::o;27811:366::-;27953:3;27974:67;28038:2;28033:3;27974:67;:::i;:::-;27967:74;;28050:93;28139:3;28050:93;:::i;:::-;28168:2;28163:3;28159:12;28152:19;;27811:366;;;:::o;28183:419::-;28349:4;28387:2;28376:9;28372:18;28364:26;;28436:9;28430:4;28426:20;28422:1;28411:9;28407:17;28400:47;28464:131;28590:4;28464:131;:::i;:::-;28456:139;;28183:419;;;:::o;28608:173::-;28748:25;28744:1;28736:6;28732:14;28725:49;28608:173;:::o;28787:366::-;28929:3;28950:67;29014:2;29009:3;28950:67;:::i;:::-;28943:74;;29026:93;29115:3;29026:93;:::i;:::-;29144:2;29139:3;29135:12;29128:19;;28787:366;;;:::o;29159:419::-;29325:4;29363:2;29352:9;29348:18;29340:26;;29412:9;29406:4;29402:20;29398:1;29387:9;29383:17;29376:47;29440:131;29566:4;29440:131;:::i;:::-;29432:139;;29159:419;;;:::o;29584:176::-;29724:28;29720:1;29712:6;29708:14;29701:52;29584:176;:::o;29766:366::-;29908:3;29929:67;29993:2;29988:3;29929:67;:::i;:::-;29922:74;;30005:93;30094:3;30005:93;:::i;:::-;30123:2;30118:3;30114:12;30107:19;;29766:366;;;:::o;30138:419::-;30304:4;30342:2;30331:9;30327:18;30319:26;;30391:9;30385:4;30381:20;30377:1;30366:9;30362:17;30355:47;30419:131;30545:4;30419:131;:::i;:::-;30411:139;;30138:419;;;:::o;30563:410::-;30603:7;30626:20;30644:1;30626:20;:::i;:::-;30621:25;;30660:20;30678:1;30660:20;:::i;:::-;30655:25;;30715:1;30712;30708:9;30737:30;30755:11;30737:30;:::i;:::-;30726:41;;30916:1;30907:7;30903:15;30900:1;30897:22;30877:1;30870:9;30850:83;30827:139;;30946:18;;:::i;:::-;30827:139;30611:362;30563:410;;;;:::o;30979:185::-;31019:1;31036:20;31054:1;31036:20;:::i;:::-;31031:25;;31070:20;31088:1;31070:20;:::i;:::-;31065:25;;31109:1;31099:35;;31114:18;;:::i;:::-;31099:35;31156:1;31153;31149:9;31144:14;;30979:185;;;;:::o;31170:177::-;31310:29;31306:1;31298:6;31294:14;31287:53;31170:177;:::o;31353:366::-;31495:3;31516:67;31580:2;31575:3;31516:67;:::i;:::-;31509:74;;31592:93;31681:3;31592:93;:::i;:::-;31710:2;31705:3;31701:12;31694:19;;31353:366;;;:::o;31725:419::-;31891:4;31929:2;31918:9;31914:18;31906:26;;31978:9;31972:4;31968:20;31964:1;31953:9;31949:17;31942:47;32006:131;32132:4;32006:131;:::i;:::-;31998:139;;31725:419;;;:::o;32150:143::-;32207:5;32238:6;32232:13;32223:22;;32254:33;32281:5;32254:33;:::i;:::-;32150:143;;;;:::o;32299:351::-;32369:6;32418:2;32406:9;32397:7;32393:23;32389:32;32386:119;;;32424:79;;:::i;:::-;32386:119;32544:1;32569:64;32625:7;32616:6;32605:9;32601:22;32569:64;:::i;:::-;32559:74;;32515:128;32299:351;;;;:::o;32656:77::-;32693:7;32722:5;32711:16;;32656:77;;;:::o;32739:118::-;32826:24;32844:5;32826:24;:::i;:::-;32821:3;32814:37;32739:118;;:::o;32863:101::-;32899:7;32939:18;32932:5;32928:30;32917:41;;32863:101;;;:::o;32970:115::-;33055:23;33072:5;33055:23;:::i;:::-;33050:3;33043:36;32970:115;;:::o;33091:89::-;33127:7;33167:6;33160:5;33156:18;33145:29;;33091:89;;;:::o;33186:115::-;33271:23;33288:5;33271:23;:::i;:::-;33266:3;33259:36;33186:115;;:::o;33307:::-;33392:23;33409:5;33392:23;:::i;:::-;33387:3;33380:36;33307:115;;:::o;33428:136::-;33475:9;33508:50;33525:32;33534:22;33550:5;33534:22;:::i;:::-;33525:32;:::i;:::-;33508:50;:::i;:::-;33495:63;;33428:136;;;:::o;33570:125::-;33654:34;33682:5;33654:34;:::i;:::-;33649:3;33642:47;33570:125;;:::o;33701:646::-;33897:4;33935:3;33924:9;33920:19;33912:27;;33949:71;34017:1;34006:9;34002:17;33993:6;33949:71;:::i;:::-;34030:70;34096:2;34085:9;34081:18;34072:6;34030:70;:::i;:::-;34110;34176:2;34165:9;34161:18;34152:6;34110:70;:::i;:::-;34190;34256:2;34245:9;34241:18;34232:6;34190:70;:::i;:::-;34270;34335:3;34324:9;34320:19;34311:6;34270:70;:::i;:::-;33701:646;;;;;;;;:::o;34353:434::-;34498:4;34536:2;34525:9;34521:18;34513:26;;34549:71;34617:1;34606:9;34602:17;34593:6;34549:71;:::i;:::-;34630:68;34694:2;34683:9;34679:18;34670:6;34630:68;:::i;:::-;34708:72;34776:2;34765:9;34761:18;34752:6;34708:72;:::i;:::-;34353:434;;;;;;:::o;34793:167::-;34933:19;34929:1;34921:6;34917:14;34910:43;34793:167;:::o;34966:366::-;35108:3;35129:67;35193:2;35188:3;35129:67;:::i;:::-;35122:74;;35205:93;35294:3;35205:93;:::i;:::-;35323:2;35318:3;35314:12;35307:19;;34966:366;;;:::o;35338:419::-;35504:4;35542:2;35531:9;35527:18;35519:26;;35591:9;35585:4;35581:20;35577:1;35566:9;35562:17;35555:47;35619:131;35745:4;35619:131;:::i;:::-;35611:139;;35338:419;;;:::o;35763:175::-;35903:27;35899:1;35891:6;35887:14;35880:51;35763:175;:::o;35944:366::-;36086:3;36107:67;36171:2;36166:3;36107:67;:::i;:::-;36100:74;;36183:93;36272:3;36183:93;:::i;:::-;36301:2;36296:3;36292:12;36285:19;;35944:366;;;:::o;36316:419::-;36482:4;36520:2;36509:9;36505:18;36497:26;;36569:9;36563:4;36559:20;36555:1;36544:9;36540:17;36533:47;36597:131;36723:4;36597:131;:::i;:::-;36589:139;;36316:419;;;:::o;36741:177::-;36881:29;36877:1;36869:6;36865:14;36858:53;36741:177;:::o;36924:366::-;37066:3;37087:67;37151:2;37146:3;37087:67;:::i;:::-;37080:74;;37163:93;37252:3;37163:93;:::i;:::-;37281:2;37276:3;37272:12;37265:19;;36924:366;;;:::o;37296:419::-;37462:4;37500:2;37489:9;37485:18;37477:26;;37549:9;37543:4;37539:20;37535:1;37524:9;37520:17;37513:47;37577:131;37703:4;37577:131;:::i;:::-;37569:139;;37296:419;;;:::o;37721:169::-;37861:21;37857:1;37849:6;37845:14;37838:45;37721:169;:::o;37896:366::-;38038:3;38059:67;38123:2;38118:3;38059:67;:::i;:::-;38052:74;;38135:93;38224:3;38135:93;:::i;:::-;38253:2;38248:3;38244:12;38237:19;;37896:366;;;:::o;38268:419::-;38434:4;38472:2;38461:9;38457:18;38449:26;;38521:9;38515:4;38511:20;38507:1;38496:9;38492:17;38485:47;38549:131;38675:4;38549:131;:::i;:::-;38541:139;;38268:419;;;:::o;38693:230::-;38833:34;38829:1;38821:6;38817:14;38810:58;38902:13;38897:2;38889:6;38885:15;38878:38;38693:230;:::o;38929:366::-;39071:3;39092:67;39156:2;39151:3;39092:67;:::i;:::-;39085:74;;39168:93;39257:3;39168:93;:::i;:::-;39286:2;39281:3;39277:12;39270:19;;38929:366;;;:::o;39301:419::-;39467:4;39505:2;39494:9;39490:18;39482:26;;39554:9;39548:4;39544:20;39540:1;39529:9;39525:17;39518:47;39582:131;39708:4;39582:131;:::i;:::-;39574:139;;39301:419;;;:::o;39726:188::-;39764:3;39783:18;39799:1;39783:18;:::i;:::-;39778:23;;39815:18;39831:1;39815:18;:::i;:::-;39810:23;;39856:1;39853;39849:9;39842:16;;39879:4;39874:3;39871:13;39868:39;;;39887:18;;:::i;:::-;39868:39;39726:188;;;;:::o;39920:170::-;39950:1;39967:18;39983:1;39967:18;:::i;:::-;39962:23;;39999:18;40015:1;39999:18;:::i;:::-;39994:23;;40036:1;40026:35;;40041:18;;:::i;:::-;40026:35;40082:1;40079;40075:9;40070:14;;39920:170;;;;:::o
Swarm Source
ipfs://3e818d623af3e238fd9d52509a69be771a4adb8114d3e2519f4dd447a4e75523
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.