Source Code
Overview
XDC Balance
XDC Value
$0.00Cross-Chain Transactions
Loading...
Loading
Contract Name:
Dice
Compiler Version
v0.8.20+commit.a1b79de6
Contract Source Code (Solidity)
/**
*Submitted for verification at xdcscan.com on 2025-08-20
*/
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/dice.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 chosenNumber;
uint8 result;
uint256 amount;
uint256 payout;
}
/**
* @title Bet History Structure
* @dev Records individual bet data with optimized storage
*/
struct BetHistory {
uint8 chosenNumber;
uint8 rolledNumber;
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 Dice
* @dev Provably fair dice game using VRF for randomness
*/
contract Dice is ReentrancyGuard, Pausable, VRFConsumerBaseV2, Ownable {
// ============ Events ============
event BetPlaced(address indexed player, uint256 requestId, uint8 chosenNumber, 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 private constant MAX_NUMBER = 6;
uint8 public constant MAX_HISTORY_SIZE = 10;
uint256 public constant DENOMINATOR = 10000;
uint256 public constant MAX_BET_AMOUNT = 10_000_000 * 10**18;
uint256 public constant MAX_POSSIBLE_PAYOUT = 60_000_000 * 10**18; // 10M * 6
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 dice number
* @param chosenNumber Number to bet on (1-6)
* @param amount Token amount to bet
* @return requestId VRF request ID
*/
function playDice(uint8 chosenNumber, 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 (chosenNumber < 1 || chosenNumber > MAX_NUMBER) revert InvalidBetParameters("Invalid chosen number");
// 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 * 6;
if (potentialPayout / 6 != 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,
chosenNumber: chosenNumber,
result: 0,
amount: amount,
payout: 0
});
user.currentRequestId = requestId;
emit BetPlaced(msg.sender, requestId, chosenNumber, 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 chosenNumber = user.currentGame.chosenNumber;
uint256 betAmount = user.currentGame.amount;
// ===== EFFECTS =====
// 1. Calculate result
uint8 result = uint8(randomWords[0] % MAX_NUMBER + 1);
// 2. Calculate payout
uint256 payout = 0;
if (chosenNumber == result) {
// Ensure safe multiplication
if (betAmount > type(uint256).max / 6) {
revert PayoutCalculationError("Bet amount too large for payout calculation");
}
payout = betAmount * 6;
}
// 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,
chosenNumber,
result,
betAmount,
payout
);
// 6. Update global statistics
unchecked { ++totalGamesPlayed; }
if (payout > 0) {
totalPayoutAmount += payout;
}
// Cleanup
delete requestToPlayer[requestId];
delete activeRequestIds[requestId];
delete s_requests[requestId];
user.currentRequestId = 0;
// ===== 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.chosenNumber,
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.chosenNumber,
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 chosenNumber,
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;
chosenNumber = user.currentGame.chosenNumber;
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 1-6
isWin = payout > 0 && result > 0 && result <= MAX_NUMBER;
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 chosenNumber Player's selected number
* @param result Roll result
* @param amount Bet amount
* @param payout Win amount
*/
function _updateUserHistory(
UserData storage user,
uint8 chosenNumber,
uint8 result,
uint256 amount,
uint256 payout
) private {
BetHistory memory newBet = BetHistory({
chosenNumber: chosenNumber,
rolledNumber: 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":"chosenNumber","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":"DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":[{"internalType":"address","name":"player","type":"address"}],"name":"canStartNewGame","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","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":"chosenNumber","type":"uint8"},{"internalType":"uint8","name":"rolledNumber","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":"chosenNumber","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":"chosenNumber","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":"uint8","name":"chosenNumber","type":"uint8"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"playDice","outputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"stateMutability":"nonpayable","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
61018060405234801562000011575f80fd5b5060405162004dcc38038062004dcc833981810160405281019062000037919062000594565b338660015f819055508073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000e8575f6040517f1e4fbdf7000000000000000000000000000000000000000000000000000000008152600401620000df919062000653565b60405180910390fd5b620000f9816200033e60201b60201c565b505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff16036200016b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200016290620006cc565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1603620001dc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620001d3906200073a565b60405180910390fd5b5f8363ffffffff161162000227576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200021e90620007ce565b60405180910390fd5b5f8160ff16116200026f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000266906200083c565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250508573ffffffffffffffffffffffffffffffffffffffff1660c08173ffffffffffffffffffffffffffffffffffffffff16815250508467ffffffffffffffff1660e08167ffffffffffffffff16815250508361010081815250508263ffffffff166101208163ffffffff16815250508161ffff166101408161ffff16815250508060ff166101608160ff1681525050505050505050506200085c565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816001806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6200042f8262000404565b9050919050565b620004418162000423565b81146200044c575f80fd5b50565b5f815190506200045f8162000436565b92915050565b5f67ffffffffffffffff82169050919050565b620004838162000465565b81146200048e575f80fd5b50565b5f81519050620004a18162000478565b92915050565b5f819050919050565b620004bb81620004a7565b8114620004c6575f80fd5b50565b5f81519050620004d981620004b0565b92915050565b5f63ffffffff82169050919050565b620004f981620004df565b811462000504575f80fd5b50565b5f815190506200051781620004ee565b92915050565b5f61ffff82169050919050565b62000535816200051d565b811462000540575f80fd5b50565b5f8151905062000553816200052a565b92915050565b5f60ff82169050919050565b620005708162000559565b81146200057b575f80fd5b50565b5f815190506200058e8162000565565b92915050565b5f805f805f805f60e0888a031215620005b257620005b162000400565b5b5f620005c18a828b016200044f565b9750506020620005d48a828b016200044f565b9650506040620005e78a828b0162000491565b9550506060620005fa8a828b01620004c9565b94505060806200060d8a828b0162000507565b93505060a0620006208a828b0162000543565b92505060c0620006338a828b016200057e565b91505092959891949750929550565b6200064d8162000423565b82525050565b5f602082019050620006685f83018462000642565b92915050565b5f82825260208201905092915050565b7f546f6b656e20616464726573732063616e6e6f74206265207a65726f000000005f82015250565b5f620006b4601c836200066e565b9150620006c1826200067e565b602082019050919050565b5f6020820190508181035f830152620006e581620006a6565b9050919050565b7f56524620636f6f7264696e61746f722063616e6e6f74206265207a65726f00005f82015250565b5f62000722601e836200066e565b91506200072f82620006ec565b602082019050919050565b5f6020820190508181035f830152620007538162000714565b9050919050565b7f43616c6c6261636b20676173206c696d69742063616e6e6f74206265207a65725f8201527f6f00000000000000000000000000000000000000000000000000000000000000602082015250565b5f620007b66021836200066e565b9150620007c3826200075a565b604082019050919050565b5f6020820190508181035f830152620007e781620007a8565b9050919050565b7f4e756d626572206f6620776f7264732063616e6e6f74206265207a65726f00005f82015250565b5f62000824601e836200066e565b91506200083182620007ee565b602082019050919050565b5f6020820190508181035f830152620008558162000816565b9050919050565b60805160a05160c05160e051610100516101205161014051610160516144c7620009055f395f8181611cbd015261223801525f611c7b01525f611c9c01525f611c3901525f611c5a01525f611bfd01525f8181610dc50152818161123d0152818161174201528181611a8701528181611b5d0152818161272701528181612cd301528181612d7201528181612e480152612ee901525f81816105c2015261061601526144c75ff3fe608060405234801561000f575f80fd5b50600436106101a7575f3560e01c8063715018a6116100f7578063c106625111610095578063e7efcfc21161006f578063e7efcfc214610481578063f2fde38b1461049f578063f3b5a671146104bb578063ffc9896b146104eb576101a7565b8063c106625114610415578063ce84a53b14610433578063d56ec8c914610463576101a7565b8063918f8674116100d1578063918f8674146103a0578063a168fa89146103be578063a20ff9fc146103ef578063b52a0855146103f9576101a7565b8063715018a61461036e5780638456cb59146103785780638da5cb5b14610382576101a7565b80634689b441116101645780635bdb2e1b1161013e5780635bdb2e1b146102e45780635c492129146103025780635c975abb146103205780635d424b851461033e576101a7565b80634689b4411461026d57806354e4437e1461028b57806357816f3e146102c6576101a7565b806304243151146101ab5780630c99375d146101db5780631fe543e31461020b578063309167f51461022757806338f8bc6d146102455780633f4ba83a14610263575b5f80fd5b6101c560048036038101906101c09190613141565b61051c565b6040516101d291906131ab565b60405180910390f35b6101f560048036038101906101f091906131ee565b610555565b6040516102029190613233565b60405180910390f35b6102256004803603810190610220919061339c565b6105c0565b005b61022f610680565b60405161023c9190613405565b60405180910390f35b61024d61068f565b60405161025a9190613439565b60405180910390f35b61026b610694565b005b6102756106b6565b6040516102829190613405565b60405180910390f35b6102a560048036038101906102a091906131ee565b6106c5565b6040516102bd9c9b9a99989796959493929190613452565b60405180910390f35b6102ce6108fa565b6040516102db9190613439565b60405180910390f35b6102ec6108ff565b6040516102f99190613405565b60405180910390f35b61030a610905565b6040516103179190613405565b60405180910390f35b61032861090b565b6040516103359190613233565b60405180910390f35b610358600480360381019061035391906131ee565b610920565b6040516103659190613233565b60405180910390f35b61037661098b565b005b61038061099e565b005b61038a6109c0565b60405161039791906131ab565b60405180910390f35b6103a86109e7565b6040516103b59190613405565b60405180910390f35b6103d860048036038101906103d39190613141565b6109ed565b6040516103e692919061350a565b60405180910390f35b6103f7610a25565b005b610413600480360381019061040e91906131ee565b610ecb565b005b61041d611344565b60405161042a9190613439565b60405180910390f35b61044d600480360381019061044891906131ee565b611349565b60405161045a919061367b565b60405180910390f35b61046b611740565b60405161047891906136f6565b60405180910390f35b610489611764565b6040516104969190613405565b60405180910390f35b6104b960048036038101906104b491906131ee565b61176a565b005b6104d560048036038101906104d09190613739565b6117ee565b6040516104e29190613405565b60405180910390f35b610505600480360381019061050091906131ee565b61200c565b6040516105139291906137ff565b60405180910390f35b5f60075f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1680156105b857505f816003015414155b915050919050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461067257337f00000000000000000000000000000000000000000000000000000000000000006040517f1cf993f4000000000000000000000000000000000000000000000000000000008152600401610669929190613826565b60405180910390fd5b61067c828261217a565b5050565b6a084595161401484a00000081565b60fe81565b61069c612814565b6106a461289b565b6106ac6128df565b6106b4612940565b565b6a31a17e847807b1bc00000081565b5f805f805f805f805f805f805f73ffffffffffffffffffffffffffffffffffffffff168d73ffffffffffffffffffffffffffffffffffffffff160361073f576040517f34460f41000000000000000000000000000000000000000000000000000000008152600401610736906138a7565b60405180910390fd5b5f60025f8f73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff169c50805f015f0160019054906101000a900460ff169a50805f015f0160029054906101000a900460ff169950805f01600101549850805f015f0160039054906101000a900460ff169750805f0160020154965080600301549550806005015f9054906101000a900463ffffffff1663ffffffff1691505f8711801561081857505f8860ff16115b801561082b5750600660ff168860ff1611155b9b505f94505f93505f8614610875575f60065f8881526020019081526020015f209050805f0160019054906101000a900460ff169550805f015f9054906101000a900460ff169450505b5f92508c156108ea575f61012c826006015461089191906138f2565b431190505f610e10836005015f9054906101000a900463ffffffff166108b79190613925565b63ffffffff16421190505f8089141580156108cf5750875b90508280156108db5750815b80156108e45750805b95505050505b5091939597999b5091939597999b565b60ff81565b60055481565b60035481565b5f60015f9054906101000a900460ff16905090565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1615801561098357505f8160030154145b915050919050565b610993612814565b61099c5f612949565b565b6109a6612814565b6109ae61289b565b6109b6612a0b565b6109be612940565b565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b61271081565b6006602052805f5260405f205f91509050805f015f9054906101000a900460ff1690805f0160019054906101000a900460ff16905082565b610a2d61289b565b610a35612a6c565b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610ac6576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610abd906139a6565b60405180910390fd5b5f816003015490505f8103610b10576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610b0790613a0e565b60405180910390fd5b60065f8281526020019081526020015f205f015f9054906101000a900460ff168015610b4c5750600a8260060154610b4891906138f2565b4311155b15610b8c576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610b8390613a9c565b60405180910390fd5b5f61012c8360060154610b9f91906138f2565b431190505f610e10846005015f9054906101000a900463ffffffff16610bc59190613925565b63ffffffff16421190505f808414158015610bfd575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b9050821580610c0a575081155b80610c13575080155b15610c53576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610c4a90613b2a565b60405180910390fd5b5f855f016001015490505f8103610c9f576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610c9690613b92565b60405180910390fd5b60065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f610ce59190613009565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690556001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060ff865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166340c10f1933836040518363ffffffff1660e01b8152600401610e1e929190613bb0565b5f604051808303815f87803b158015610e35575f80fd5b505af1158015610e47573d5f803e3d5ffd5b50505050610e6b86875f015f0160029054906101000a900460ff1660ff8485612aad565b3373ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d538683604051610eb3929190613bd7565b60405180910390a2505050505050610ec9612940565b565b610ed3612814565b610edb61289b565b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610f6c576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610f63906139a6565b60405180910390fd5b5f816003015490505f8114158015610fa0575060065f8281526020019081526020015f205f015f9054906101000a900460ff165b8015610fbc5750600a8260060154610fb891906138f2565b4311155b15610ffc576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610ff390613a9c565b60405180910390fd5b5f61012c836006015461100f91906138f2565b431190505f610e10846005015f9054906101000a900463ffffffff166110359190613925565b63ffffffff16421190505f80841415801561106d575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b905082158061107a575081155b80611083575080155b156110c3576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016110ba90613c6e565b60405180910390fd5b5f855f016001015490505f810361110f576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161110690613b92565b60405180910390fd5b5f85146111b25760075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff021916905560065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6111af9190613009565b50505b6001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060fe865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166340c10f1988836040518363ffffffff1660e01b8152600401611296929190613bb0565b5f604051808303815f87803b1580156112ad575f80fd5b505af11580156112bf573d5f803e3d5ffd5b505050506112e386875f015f0160029054906101000a900460ff1660fe8485612aad565b8673ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d53868360405161132b929190613bd7565b60405180910390a2505050505050611341612940565b50565b600a81565b60605f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036113b9576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016113b0906138a7565b60405180910390fd5b5f60025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2090505f816004018054905090505f8103611468575f67ffffffffffffffff81111561142557611424613260565b5b60405190808252806020026020018201604052801561145e57816020015b61144b613027565b8152602001906001900390816114435790505b509250505061173b565b5f600a60ff16821161147a5781611480565b600a60ff165b90505f8167ffffffffffffffff81111561149d5761149c613260565b5b6040519080825280602002602001820160405280156114d657816020015b6114c3613027565b8152602001906001900390816114bb5790505b509050600a60ff168310156115da575f5b838110156115d45784600401816001866115019190613c8c565b61150b9190613c8c565b8154811061151c5761151b613cbf565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff168152602001600182015481526020016002820154815250508282815181106115b6576115b5613cbf565b5b602002602001018190525080806115cc90613cec565b9150506114e7565b50611733565b5f80856007015f9054906101000a900460ff1660ff1614611617576001856007015f9054906101000a900460ff166116129190613d33565b611626565b6001600a6116259190613d33565b5b60ff1690505f5b600a60ff168110156117305785600401600a60ff1682600a60ff168561165391906138f2565b61165d9190613c8c565b6116679190613d94565b8154811061167857611677613cbf565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016001820154815260200160028201548152505083828151811061171257611711613cbf565b5b6020026020010181905250808061172890613cec565b91505061162d565b50505b809450505050505b919050565b7f000000000000000000000000000000000000000000000000000000000000000081565b60045481565b611772612814565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036117e2575f6040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016117d991906131ab565b60405180910390fd5b6117eb81612949565b50565b5f6117f761289b565b6117ff612a6c565b5f8203611841576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161183890613e0e565b60405180910390fd5b6a084595161401484a00000082111561188f576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161188690613e76565b60405180910390fd5b60018360ff1610806118a75750600660ff168360ff16115b156118e7576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016118de90613ede565b60405180910390fd5b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1615611979576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161197090613f46565b60405180910390fd5b5f8160030154146119bf576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016119b690613fae565b60405180910390fd5b6119c93384612cd0565b5f6006846119d79190613fcc565b9050836006826119e7919061400d565b14611a27576040517f327512f6000000000000000000000000000000000000000000000000000000008152600401611a1e90614087565b60405180910390fd5b6a31a17e847807b1bc000000811115611a8457806a31a17e847807b1bc0000006040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611a7b929190613bd7565b60405180910390fd5b5f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16636f97e31c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611aee573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b1291906140b9565b905080821115611b5b5781816040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611b52929190613bd7565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166379cc679033876040518363ffffffff1660e01b8152600401611bb6929190613bb0565b5f604051808303815f87803b158015611bcd575f80fd5b505af1158015611bdf573d5f803e3d5ffd5b505050508460055f828254611bf491906138f2565b925050819055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16635d3b1d307f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006040518663ffffffff1660e01b8152600401611cfc959493929190614179565b6020604051808303815f875af1158015611d18573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d3c91906140b9565b935060405180606001604052805f151581526020016001151581526020015f67ffffffffffffffff811115611d7457611d73613260565b5b604051908082528060200260200182016040528015611da25781602001602082028036833780820191505090505b5081525060065f8681526020019081526020015f205f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151816001019080519060200190611e0f92919061305d565b509050503360075f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160085f8681526020019081526020015f205f6101000a81548160ff02191690831515021790555042836005015f6101000a81548163ffffffff021916908363ffffffff1602179055504383600601819055505f8360070160016101000a81548160ff0219169083151502179055506040518060c001604052806001151581526020015f151581526020018760ff1681526020015f60ff1681526020018681526020015f815250835f015f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151815f0160026101000a81548160ff021916908360ff1602179055506060820151815f0160036101000a81548160ff021916908360ff1602179055506080820151816001015560a082015181600201559050508383600301819055503373ffffffffffffffffffffffffffffffffffffffff167f1efa8a4db358fbd4b3e1004cca7c5a1bdc9de3512d3392d48a51d1961316f983858888604051611ff3939291906141ca565b60405180910390a2505050612006612940565b92915050565b6120146130a8565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603612083576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161207a906138a7565b60405180910390fd5b5f60025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f01816005015f9054906101000a900463ffffffff16816040518060c00160405290815f82015f9054906101000a900460ff161515151581526020015f820160019054906101000a900460ff161515151581526020015f820160029054906101000a900460ff1660ff1660ff1681526020015f820160039054906101000a900460ff1660ff1660ff1681526020016001820154815260200160028201548152505091508063ffffffff1690509250925050915091565b61218261289b565b5f60065f8481526020019081526020015f209050805f0160019054906101000a900460ff166121e6576040517f1726a07e0000000000000000000000000000000000000000000000000000000081526004016121dd90614249565b60405180910390fd5b805f015f9054906101000a900460ff1615612236576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161222d906142b1565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000060ff1682511461229c576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161229390614319565b60405180910390fd5b5f60075f8581526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361233e576040517f1726a07e000000000000000000000000000000000000000000000000000000008152600401612335906138a7565b60405180910390fd5b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050848160030154146123c4576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016123bb90614381565b60405180910390fd5b6001835f015f6101000a81548160ff021916908315150217905550838360010190805190602001906123f792919061305d565b5060018160070160016101000a81548160ff021916908315150217905550805f015f015f9054906101000a900460ff166124f35760065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6124719190613009565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690555f81600301819055505f8160070160016101000a81548160ff021916908315150217905550505050612808565b5f815f015f0160029054906101000a900460ff1690505f825f016001015490505f6001600660ff16885f8151811061252e5761252d613cbf565b5b60200260200101516125409190613d94565b61254a91906138f2565b90505f8160ff168460ff16036125d95760067fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff612587919061400d565b8311156125c9576040517f327512f60000000000000000000000000000000000000000000000000000000081526004016125c09061440f565b60405180910390fd5b6006836125d69190613fcc565b90505b81855f015f0160036101000a81548160ff021916908360ff1602179055505f855f015f015f6101000a81548160ff0219169083151502179055506001855f015f0160016101000a81548160ff02191690831515021790555080855f01600201819055506126498585848685612aad565b60035f8154600101919050819055505f811115612679578060045f82825461267191906138f2565b925050819055505b60075f8a81526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8a81526020019081526020015f205f6101000a81549060ff021916905560065f8a81526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6127129190613009565b50505f85600301819055505f8111156127ae577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166340c10f1987836040518363ffffffff1660e01b8152600401612780929190613bb0565b5f604051808303815f87803b158015612797575f80fd5b505af11580156127a9573d5f803e3d5ffd5b505050505b8573ffffffffffffffffffffffffffffffffffffffff167fc74777e2214d32e7b4952c2ecbbfc0ec7dc3642e448c3c927261cb69d9f036508a84846040516127f8939291906141ca565b60405180910390a2505050505050505b612810612940565b5050565b61281c612fc2565b73ffffffffffffffffffffffffffffffffffffffff1661283a6109c0565b73ffffffffffffffffffffffffffffffffffffffff16146128995761285d612fc2565b6040517f118cdaa700000000000000000000000000000000000000000000000000000000815260040161289091906131ab565b60405180910390fd5b565b60025f54036128d6576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60025f81905550565b6128e7612fc9565b5f60015f6101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa612929612fc2565b60405161293691906131ab565b60405180910390a1565b60015f81905550565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816001806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b612a13612a6c565b6001805f6101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612a55612fc2565b604051612a6291906131ab565b60405180910390a1565b612a7461090b565b15612aab576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f6040518060a001604052808660ff1681526020018560ff1681526020014263ffffffff168152602001848152602001838152509050600a60ff1686600401805490501015612bd2578560040181908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff16021790555060608201518160010155608082015181600201555050600a60ff168660040180549050612bb29190613d94565b866007015f6101000a81548160ff021916908360ff160217905550612cc8565b8086600401876007015f9054906101000a900460ff1660ff1681548110612bfc57612bfb613cbf565b5b905f5260205f2090600302015f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff1602179055506060820151816001015560808201518160020155905050600a6001876007015f9054906101000a900460ff16612ca2919061442d565b612cac9190614461565b866007015f6101000a81548160ff021916908360ff1602179055505b505050505050565b807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612d2a91906131ab565b602060405180830381865afa158015612d45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612d6991906140b9565b1015612e4557807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612dc991906131ab565b602060405180830381865afa158015612de4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e0891906140b9565b6040517f5f504d20000000000000000000000000000000000000000000000000000000008152600401612e3c929190613bd7565b60405180910390fd5b807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612ea1929190613826565b602060405180830381865afa158015612ebc573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ee091906140b9565b1015612fbe57807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612f42929190613826565b602060405180830381865afa158015612f5d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f8191906140b9565b6040517f2a1b2dd8000000000000000000000000000000000000000000000000000000008152600401612fb5929190613bd7565b60405180910390fd5b5050565b5f33905090565b612fd161090b565b613007576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5080545f8255905f5260205f209081019061302491906130e2565b50565b6040518060a001604052805f60ff1681526020015f60ff1681526020015f63ffffffff1681526020015f81526020015f81525090565b828054828255905f5260205f20908101928215613097579160200282015b8281111561309657825182559160200191906001019061307b565b5b5090506130a491906130e2565b5090565b6040518060c001604052805f151581526020015f151581526020015f60ff1681526020015f60ff1681526020015f81526020015f81525090565b5b808211156130f9575f815f9055506001016130e3565b5090565b5f604051905090565b5f80fd5b5f80fd5b5f819050919050565b6131208161310e565b811461312a575f80fd5b50565b5f8135905061313b81613117565b92915050565b5f6020828403121561315657613155613106565b5b5f6131638482850161312d565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6131958261316c565b9050919050565b6131a58161318b565b82525050565b5f6020820190506131be5f83018461319c565b92915050565b6131cd8161318b565b81146131d7575f80fd5b50565b5f813590506131e8816131c4565b92915050565b5f6020828403121561320357613202613106565b5b5f613210848285016131da565b91505092915050565b5f8115159050919050565b61322d81613219565b82525050565b5f6020820190506132465f830184613224565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61329682613250565b810181811067ffffffffffffffff821117156132b5576132b4613260565b5b80604052505050565b5f6132c76130fd565b90506132d3828261328d565b919050565b5f67ffffffffffffffff8211156132f2576132f1613260565b5b602082029050602081019050919050565b5f80fd5b5f613319613314846132d8565b6132be565b9050808382526020820190506020840283018581111561333c5761333b613303565b5b835b818110156133655780613351888261312d565b84526020840193505060208101905061333e565b5050509392505050565b5f82601f8301126133835761338261324c565b5b8135613393848260208601613307565b91505092915050565b5f80604083850312156133b2576133b1613106565b5b5f6133bf8582860161312d565b925050602083013567ffffffffffffffff8111156133e0576133df61310a565b5b6133ec8582860161336f565b9150509250929050565b6133ff8161310e565b82525050565b5f6020820190506134185f8301846133f6565b92915050565b5f60ff82169050919050565b6134338161341e565b82525050565b5f60208201905061344c5f83018461342a565b92915050565b5f610180820190506134665f83018f613224565b613473602083018e613224565b613480604083018d613224565b61348d606083018c61342a565b61349a608083018b6133f6565b6134a760a083018a61342a565b6134b460c08301896133f6565b6134c160e08301886133f6565b6134cf610100830187613224565b6134dd610120830186613224565b6134eb610140830185613224565b6134f96101608301846133f6565b9d9c50505050505050505050505050565b5f60408201905061351d5f830185613224565b61352a6020830184613224565b9392505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6135638161341e565b82525050565b5f63ffffffff82169050919050565b61358181613569565b82525050565b6135908161310e565b82525050565b60a082015f8201516135aa5f85018261355a565b5060208201516135bd602085018261355a565b5060408201516135d06040850182613578565b5060608201516135e36060850182613587565b5060808201516135f66080850182613587565b50505050565b5f6136078383613596565b60a08301905092915050565b5f602082019050919050565b5f61362982613531565b613633818561353b565b935061363e8361354b565b805f5b8381101561366e57815161365588826135fc565b975061366083613613565b925050600181019050613641565b5085935050505092915050565b5f6020820190508181035f830152613693818461361f565b905092915050565b5f819050919050565b5f6136be6136b96136b48461316c565b61369b565b61316c565b9050919050565b5f6136cf826136a4565b9050919050565b5f6136e0826136c5565b9050919050565b6136f0816136d6565b82525050565b5f6020820190506137095f8301846136e7565b92915050565b6137188161341e565b8114613722575f80fd5b50565b5f813590506137338161370f565b92915050565b5f806040838503121561374f5761374e613106565b5b5f61375c85828601613725565b925050602061376d8582860161312d565b9150509250929050565b61378081613219565b82525050565b60c082015f82015161379a5f850182613777565b5060208201516137ad6020850182613777565b5060408201516137c0604085018261355a565b5060608201516137d3606085018261355a565b5060808201516137e66080850182613587565b5060a08201516137f960a0850182613587565b50505050565b5f60e0820190506138125f830185613786565b61381f60c08301846133f6565b9392505050565b5f6040820190506138395f83018561319c565b613846602083018461319c565b9392505050565b5f82825260208201905092915050565b7f496e76616c696420706c617965722061646472657373000000000000000000005f82015250565b5f61389160168361384d565b915061389c8261385d565b602082019050919050565b5f6020820190508181035f8301526138be81613885565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6138fc8261310e565b91506139078361310e565b925082820190508082111561391f5761391e6138c5565b5b92915050565b5f61392f82613569565b915061393a83613569565b9250828201905063ffffffff811115613956576139556138c5565b5b92915050565b7f4e6f206163746976652067616d650000000000000000000000000000000000005f82015250565b5f613990600e8361384d565b915061399b8261395c565b602082019050919050565b5f6020820190508181035f8301526139bd81613984565b9050919050565b7f4e6f2070656e64696e67207265717565737420746f207265636f7665720000005f82015250565b5f6139f8601d8361384d565b9150613a03826139c4565b602082019050919050565b5f6020820190508181035f830152613a25816139ec565b9050919050565b7f52657175657374206a7573742066756c66696c6c65642c206c657420565246205f8201527f636f6d706c657465000000000000000000000000000000000000000000000000602082015250565b5f613a8660288361384d565b9150613a9182613a2c565b604082019050919050565b5f6020820190508181035f830152613ab381613a7a565b9050919050565b7f47616d65206e6f7420656c696769626c6520666f72207265636f7665727920795f8201527f6574000000000000000000000000000000000000000000000000000000000000602082015250565b5f613b1460228361384d565b9150613b1f82613aba565b604082019050919050565b5f6020820190508181035f830152613b4181613b08565b9050919050565b7f4e6f7468696e6720746f20726566756e640000000000000000000000000000005f82015250565b5f613b7c60118361384d565b9150613b8782613b48565b602082019050919050565b5f6020820190508181035f830152613ba981613b70565b9050919050565b5f604082019050613bc35f83018561319c565b613bd060208301846133f6565b9392505050565b5f604082019050613bea5f8301856133f6565b613bf760208301846133f6565b9392505050565b7f47616d65206e6f7420656c696769626c6520666f7220666f7263652073746f705f8201527f2079657400000000000000000000000000000000000000000000000000000000602082015250565b5f613c5860248361384d565b9150613c6382613bfe565b604082019050919050565b5f6020820190508181035f830152613c8581613c4c565b9050919050565b5f613c968261310e565b9150613ca18361310e565b9250828203905081811115613cb957613cb86138c5565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f613cf68261310e565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613d2857613d276138c5565b5b600182019050919050565b5f613d3d8261341e565b9150613d488361341e565b9250828203905060ff811115613d6157613d606138c5565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f613d9e8261310e565b9150613da98361310e565b925082613db957613db8613d67565b5b828206905092915050565b7f42657420616d6f756e742063616e6e6f74206265207a65726f000000000000005f82015250565b5f613df860198361384d565b9150613e0382613dc4565b602082019050919050565b5f6020820190508181035f830152613e2581613dec565b9050919050565b7f42657420616d6f756e7420746f6f206c617267650000000000000000000000005f82015250565b5f613e6060148361384d565b9150613e6b82613e2c565b602082019050919050565b5f6020820190508181035f830152613e8d81613e54565b9050919050565b7f496e76616c69642063686f73656e206e756d62657200000000000000000000005f82015250565b5f613ec860158361384d565b9150613ed382613e94565b602082019050919050565b5f6020820190508181035f830152613ef581613ebc565b9050919050565b7f557365722068617320616e206163746976652067616d650000000000000000005f82015250565b5f613f3060178361384d565b9150613f3b82613efc565b602082019050919050565b5f6020820190508181035f830152613f5d81613f24565b9050919050565b7f557365722068617320612070656e64696e6720726571756573740000000000005f82015250565b5f613f98601a8361384d565b9150613fa382613f64565b602082019050919050565b5f6020820190508181035f830152613fc581613f8c565b9050919050565b5f613fd68261310e565b9150613fe18361310e565b9250828202613fef8161310e565b91508282048414831517614006576140056138c5565b5b5092915050565b5f6140178261310e565b91506140228361310e565b92508261403257614031613d67565b5b828204905092915050565b7f5061796f75742063616c63756c6174696f6e206f766572666c6f7700000000005f82015250565b5f614071601b8361384d565b915061407c8261403d565b602082019050919050565b5f6020820190508181035f83015261409e81614065565b9050919050565b5f815190506140b381613117565b92915050565b5f602082840312156140ce576140cd613106565b5b5f6140db848285016140a5565b91505092915050565b5f819050919050565b6140f6816140e4565b82525050565b5f67ffffffffffffffff82169050919050565b614118816140fc565b82525050565b5f61ffff82169050919050565b6141348161411e565b82525050565b61414381613569565b82525050565b5f61416361415e6141598461341e565b61369b565b613569565b9050919050565b61417381614149565b82525050565b5f60a08201905061418c5f8301886140ed565b614199602083018761410f565b6141a6604083018661412b565b6141b3606083018561413a565b6141c0608083018461416a565b9695505050505050565b5f6060820190506141dd5f8301866133f6565b6141ea602083018561342a565b6141f760408301846133f6565b949350505050565b7f52657175657374206e6f7420666f756e640000000000000000000000000000005f82015250565b5f61423360118361384d565b915061423e826141ff565b602082019050919050565b5f6020820190508181035f83015261426081614227565b9050919050565b7f5265717565737420616c72656164792066756c66696c6c6564000000000000005f82015250565b5f61429b60198361384d565b91506142a682614267565b602082019050919050565b5f6020820190508181035f8301526142c88161428f565b9050919050565b7f496e76616c69642072616e646f6d20776f726473206c656e67746800000000005f82015250565b5f614303601b8361384d565b915061430e826142cf565b602082019050919050565b5f6020820190508181035f830152614330816142f7565b9050919050565b7f52657175657374204944206d69736d61746368000000000000000000000000005f82015250565b5f61436b60138361384d565b915061437682614337565b602082019050919050565b5f6020820190508181035f8301526143988161435f565b9050919050565b7f42657420616d6f756e7420746f6f206c6172676520666f72207061796f7574205f8201527f63616c63756c6174696f6e000000000000000000000000000000000000000000602082015250565b5f6143f9602b8361384d565b91506144048261439f565b604082019050919050565b5f6020820190508181035f830152614426816143ed565b9050919050565b5f6144378261341e565b91506144428361341e565b9250828201905060ff81111561445b5761445a6138c5565b5b92915050565b5f61446b8261341e565b91506144768361341e565b92508261448657614485613d67565b5b82820690509291505056fea26469706673582212202f17750668dcdefc304ba4a04de6d2e7ac58b20582b56c65fc4d5d4fcb4c7f5a64736f6c634300081400330000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f0000000000000000000000000000000000000000000000000000000000000026c0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af00000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001
Deployed Bytecode
0x608060405234801561000f575f80fd5b50600436106101a7575f3560e01c8063715018a6116100f7578063c106625111610095578063e7efcfc21161006f578063e7efcfc214610481578063f2fde38b1461049f578063f3b5a671146104bb578063ffc9896b146104eb576101a7565b8063c106625114610415578063ce84a53b14610433578063d56ec8c914610463576101a7565b8063918f8674116100d1578063918f8674146103a0578063a168fa89146103be578063a20ff9fc146103ef578063b52a0855146103f9576101a7565b8063715018a61461036e5780638456cb59146103785780638da5cb5b14610382576101a7565b80634689b441116101645780635bdb2e1b1161013e5780635bdb2e1b146102e45780635c492129146103025780635c975abb146103205780635d424b851461033e576101a7565b80634689b4411461026d57806354e4437e1461028b57806357816f3e146102c6576101a7565b806304243151146101ab5780630c99375d146101db5780631fe543e31461020b578063309167f51461022757806338f8bc6d146102455780633f4ba83a14610263575b5f80fd5b6101c560048036038101906101c09190613141565b61051c565b6040516101d291906131ab565b60405180910390f35b6101f560048036038101906101f091906131ee565b610555565b6040516102029190613233565b60405180910390f35b6102256004803603810190610220919061339c565b6105c0565b005b61022f610680565b60405161023c9190613405565b60405180910390f35b61024d61068f565b60405161025a9190613439565b60405180910390f35b61026b610694565b005b6102756106b6565b6040516102829190613405565b60405180910390f35b6102a560048036038101906102a091906131ee565b6106c5565b6040516102bd9c9b9a99989796959493929190613452565b60405180910390f35b6102ce6108fa565b6040516102db9190613439565b60405180910390f35b6102ec6108ff565b6040516102f99190613405565b60405180910390f35b61030a610905565b6040516103179190613405565b60405180910390f35b61032861090b565b6040516103359190613233565b60405180910390f35b610358600480360381019061035391906131ee565b610920565b6040516103659190613233565b60405180910390f35b61037661098b565b005b61038061099e565b005b61038a6109c0565b60405161039791906131ab565b60405180910390f35b6103a86109e7565b6040516103b59190613405565b60405180910390f35b6103d860048036038101906103d39190613141565b6109ed565b6040516103e692919061350a565b60405180910390f35b6103f7610a25565b005b610413600480360381019061040e91906131ee565b610ecb565b005b61041d611344565b60405161042a9190613439565b60405180910390f35b61044d600480360381019061044891906131ee565b611349565b60405161045a919061367b565b60405180910390f35b61046b611740565b60405161047891906136f6565b60405180910390f35b610489611764565b6040516104969190613405565b60405180910390f35b6104b960048036038101906104b491906131ee565b61176a565b005b6104d560048036038101906104d09190613739565b6117ee565b6040516104e29190613405565b60405180910390f35b610505600480360381019061050091906131ee565b61200c565b6040516105139291906137ff565b60405180910390f35b5f60075f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1680156105b857505f816003015414155b915050919050565b7f000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461067257337f000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f6040517f1cf993f4000000000000000000000000000000000000000000000000000000008152600401610669929190613826565b60405180910390fd5b61067c828261217a565b5050565b6a084595161401484a00000081565b60fe81565b61069c612814565b6106a461289b565b6106ac6128df565b6106b4612940565b565b6a31a17e847807b1bc00000081565b5f805f805f805f805f805f805f73ffffffffffffffffffffffffffffffffffffffff168d73ffffffffffffffffffffffffffffffffffffffff160361073f576040517f34460f41000000000000000000000000000000000000000000000000000000008152600401610736906138a7565b60405180910390fd5b5f60025f8f73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff169c50805f015f0160019054906101000a900460ff169a50805f015f0160029054906101000a900460ff169950805f01600101549850805f015f0160039054906101000a900460ff169750805f0160020154965080600301549550806005015f9054906101000a900463ffffffff1663ffffffff1691505f8711801561081857505f8860ff16115b801561082b5750600660ff168860ff1611155b9b505f94505f93505f8614610875575f60065f8881526020019081526020015f209050805f0160019054906101000a900460ff169550805f015f9054906101000a900460ff169450505b5f92508c156108ea575f61012c826006015461089191906138f2565b431190505f610e10836005015f9054906101000a900463ffffffff166108b79190613925565b63ffffffff16421190505f8089141580156108cf5750875b90508280156108db5750815b80156108e45750805b95505050505b5091939597999b5091939597999b565b60ff81565b60055481565b60035481565b5f60015f9054906101000a900460ff16905090565b5f8060025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1615801561098357505f8160030154145b915050919050565b610993612814565b61099c5f612949565b565b6109a6612814565b6109ae61289b565b6109b6612a0b565b6109be612940565b565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b61271081565b6006602052805f5260405f205f91509050805f015f9054906101000a900460ff1690805f0160019054906101000a900460ff16905082565b610a2d61289b565b610a35612a6c565b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610ac6576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610abd906139a6565b60405180910390fd5b5f816003015490505f8103610b10576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610b0790613a0e565b60405180910390fd5b60065f8281526020019081526020015f205f015f9054906101000a900460ff168015610b4c5750600a8260060154610b4891906138f2565b4311155b15610b8c576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610b8390613a9c565b60405180910390fd5b5f61012c8360060154610b9f91906138f2565b431190505f610e10846005015f9054906101000a900463ffffffff16610bc59190613925565b63ffffffff16421190505f808414158015610bfd575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b9050821580610c0a575081155b80610c13575080155b15610c53576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610c4a90613b2a565b60405180910390fd5b5f855f016001015490505f8103610c9f576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610c9690613b92565b60405180910390fd5b60065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f610ce59190613009565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690556001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060ff865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166340c10f1933836040518363ffffffff1660e01b8152600401610e1e929190613bb0565b5f604051808303815f87803b158015610e35575f80fd5b505af1158015610e47573d5f803e3d5ffd5b50505050610e6b86875f015f0160029054906101000a900460ff1660ff8485612aad565b3373ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d538683604051610eb3929190613bd7565b60405180910390a2505050505050610ec9612940565b565b610ed3612814565b610edb61289b565b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff16610f6c576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610f63906139a6565b60405180910390fd5b5f816003015490505f8114158015610fa0575060065f8281526020019081526020015f205f015f9054906101000a900460ff165b8015610fbc5750600a8260060154610fb891906138f2565b4311155b15610ffc576040517f13b419a9000000000000000000000000000000000000000000000000000000008152600401610ff390613a9c565b60405180910390fd5b5f61012c836006015461100f91906138f2565b431190505f610e10846005015f9054906101000a900463ffffffff166110359190613925565b63ffffffff16421190505f80841415801561106d575060065f8581526020019081526020015f205f0160019054906101000a900460ff165b905082158061107a575081155b80611083575080155b156110c3576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016110ba90613c6e565b60405180910390fd5b5f855f016001015490505f810361110f576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161110690613b92565b60405180910390fd5b5f85146111b25760075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff021916905560065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6111af9190613009565b50505b6001865f015f0160016101000a81548160ff0219169083151502179055505f865f015f015f6101000a81548160ff02191690831515021790555060fe865f015f0160036101000a81548160ff021916908360ff16021790555080865f01600201819055505f86600301819055505f8660070160016101000a81548160ff0219169083151502179055507f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166340c10f1988836040518363ffffffff1660e01b8152600401611296929190613bb0565b5f604051808303815f87803b1580156112ad575f80fd5b505af11580156112bf573d5f803e3d5ffd5b505050506112e386875f015f0160029054906101000a900460ff1660fe8485612aad565b8673ffffffffffffffffffffffffffffffffffffffff167ff43f4cfd330c9814ec89dcb330ca1c7559745bb35a6ea4068b171116c4ea3d53868360405161132b929190613bd7565b60405180910390a2505050505050611341612940565b50565b600a81565b60605f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036113b9576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016113b0906138a7565b60405180910390fd5b5f60025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2090505f816004018054905090505f8103611468575f67ffffffffffffffff81111561142557611424613260565b5b60405190808252806020026020018201604052801561145e57816020015b61144b613027565b8152602001906001900390816114435790505b509250505061173b565b5f600a60ff16821161147a5781611480565b600a60ff165b90505f8167ffffffffffffffff81111561149d5761149c613260565b5b6040519080825280602002602001820160405280156114d657816020015b6114c3613027565b8152602001906001900390816114bb5790505b509050600a60ff168310156115da575f5b838110156115d45784600401816001866115019190613c8c565b61150b9190613c8c565b8154811061151c5761151b613cbf565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff168152602001600182015481526020016002820154815250508282815181106115b6576115b5613cbf565b5b602002602001018190525080806115cc90613cec565b9150506114e7565b50611733565b5f80856007015f9054906101000a900460ff1660ff1614611617576001856007015f9054906101000a900460ff166116129190613d33565b611626565b6001600a6116259190613d33565b5b60ff1690505f5b600a60ff168110156117305785600401600a60ff1682600a60ff168561165391906138f2565b61165d9190613c8c565b6116679190613d94565b8154811061167857611677613cbf565b5b905f5260205f2090600302016040518060a00160405290815f82015f9054906101000a900460ff1660ff1660ff1681526020015f820160019054906101000a900460ff1660ff1660ff1681526020015f820160029054906101000a900463ffffffff1663ffffffff1663ffffffff1681526020016001820154815260200160028201548152505083828151811061171257611711613cbf565b5b6020026020010181905250808061172890613cec565b91505061162d565b50505b809450505050505b919050565b7f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b81565b60045481565b611772612814565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036117e2575f6040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016117d991906131ab565b60405180910390fd5b6117eb81612949565b50565b5f6117f761289b565b6117ff612a6c565b5f8203611841576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161183890613e0e565b60405180910390fd5b6a084595161401484a00000082111561188f576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161188690613e76565b60405180910390fd5b60018360ff1610806118a75750600660ff168360ff16115b156118e7576040517f34460f410000000000000000000000000000000000000000000000000000000081526004016118de90613ede565b60405180910390fd5b5f60025f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f015f015f9054906101000a900460ff1615611979576040517f13b419a900000000000000000000000000000000000000000000000000000000815260040161197090613f46565b60405180910390fd5b5f8160030154146119bf576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016119b690613fae565b60405180910390fd5b6119c93384612cd0565b5f6006846119d79190613fcc565b9050836006826119e7919061400d565b14611a27576040517f327512f6000000000000000000000000000000000000000000000000000000008152600401611a1e90614087565b60405180910390fd5b6a31a17e847807b1bc000000811115611a8457806a31a17e847807b1bc0000006040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611a7b929190613bd7565b60405180910390fd5b5f7f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff16636f97e31c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611aee573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b1291906140b9565b905080821115611b5b5781816040517f21e510be000000000000000000000000000000000000000000000000000000008152600401611b52929190613bd7565b60405180910390fd5b7f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166379cc679033876040518363ffffffff1660e01b8152600401611bb6929190613bb0565b5f604051808303815f87803b158015611bcd575f80fd5b505af1158015611bdf573d5f803e3d5ffd5b505050508460055f828254611bf491906138f2565b925050819055507f000000000000000000000000943649947d45f06ebed0e6180c7ef04f60ab6c5f73ffffffffffffffffffffffffffffffffffffffff16635d3b1d307fc0e0e68eb6fa4cebe0ae9013a281ef5cc2143d0a23090aa3c02edd90588f74af7f00000000000000000000000000000000000000000000000000000000000000267f00000000000000000000000000000000000000000000000000000000000000037f00000000000000000000000000000000000000000000000000000000001e84807f00000000000000000000000000000000000000000000000000000000000000016040518663ffffffff1660e01b8152600401611cfc959493929190614179565b6020604051808303815f875af1158015611d18573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d3c91906140b9565b935060405180606001604052805f151581526020016001151581526020015f67ffffffffffffffff811115611d7457611d73613260565b5b604051908082528060200260200182016040528015611da25781602001602082028036833780820191505090505b5081525060065f8681526020019081526020015f205f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151816001019080519060200190611e0f92919061305d565b509050503360075f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160085f8681526020019081526020015f205f6101000a81548160ff02191690831515021790555042836005015f6101000a81548163ffffffff021916908363ffffffff1602179055504383600601819055505f8360070160016101000a81548160ff0219169083151502179055506040518060c001604052806001151581526020015f151581526020018760ff1681526020015f60ff1681526020018681526020015f815250835f015f820151815f015f6101000a81548160ff0219169083151502179055506020820151815f0160016101000a81548160ff0219169083151502179055506040820151815f0160026101000a81548160ff021916908360ff1602179055506060820151815f0160036101000a81548160ff021916908360ff1602179055506080820151816001015560a082015181600201559050508383600301819055503373ffffffffffffffffffffffffffffffffffffffff167f1efa8a4db358fbd4b3e1004cca7c5a1bdc9de3512d3392d48a51d1961316f983858888604051611ff3939291906141ca565b60405180910390a2505050612006612940565b92915050565b6120146130a8565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603612083576040517f34460f4100000000000000000000000000000000000000000000000000000000815260040161207a906138a7565b60405180910390fd5b5f60025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050805f01816005015f9054906101000a900463ffffffff16816040518060c00160405290815f82015f9054906101000a900460ff161515151581526020015f820160019054906101000a900460ff161515151581526020015f820160029054906101000a900460ff1660ff1660ff1681526020015f820160039054906101000a900460ff1660ff1660ff1681526020016001820154815260200160028201548152505091508063ffffffff1690509250925050915091565b61218261289b565b5f60065f8481526020019081526020015f209050805f0160019054906101000a900460ff166121e6576040517f1726a07e0000000000000000000000000000000000000000000000000000000081526004016121dd90614249565b60405180910390fd5b805f015f9054906101000a900460ff1615612236576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161222d906142b1565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000160ff1682511461229c576040517f1726a07e00000000000000000000000000000000000000000000000000000000815260040161229390614319565b60405180910390fd5b5f60075f8581526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361233e576040517f1726a07e000000000000000000000000000000000000000000000000000000008152600401612335906138a7565b60405180910390fd5b5f60025f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050848160030154146123c4576040517f13b419a90000000000000000000000000000000000000000000000000000000081526004016123bb90614381565b60405180910390fd5b6001835f015f6101000a81548160ff021916908315150217905550838360010190805190602001906123f792919061305d565b5060018160070160016101000a81548160ff021916908315150217905550805f015f015f9054906101000a900460ff166124f35760065f8681526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6124719190613009565b505060075f8681526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8681526020019081526020015f205f6101000a81549060ff02191690555f81600301819055505f8160070160016101000a81548160ff021916908315150217905550505050612808565b5f815f015f0160029054906101000a900460ff1690505f825f016001015490505f6001600660ff16885f8151811061252e5761252d613cbf565b5b60200260200101516125409190613d94565b61254a91906138f2565b90505f8160ff168460ff16036125d95760067fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff612587919061400d565b8311156125c9576040517f327512f60000000000000000000000000000000000000000000000000000000081526004016125c09061440f565b60405180910390fd5b6006836125d69190613fcc565b90505b81855f015f0160036101000a81548160ff021916908360ff1602179055505f855f015f015f6101000a81548160ff0219169083151502179055506001855f015f0160016101000a81548160ff02191690831515021790555080855f01600201819055506126498585848685612aad565b60035f8154600101919050819055505f811115612679578060045f82825461267191906138f2565b925050819055505b60075f8a81526020019081526020015f205f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560085f8a81526020019081526020015f205f6101000a81549060ff021916905560065f8a81526020019081526020015f205f8082015f6101000a81549060ff02191690555f820160016101000a81549060ff0219169055600182015f6127129190613009565b50505f85600301819055505f8111156127ae577f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166340c10f1987836040518363ffffffff1660e01b8152600401612780929190613bb0565b5f604051808303815f87803b158015612797575f80fd5b505af11580156127a9573d5f803e3d5ffd5b505050505b8573ffffffffffffffffffffffffffffffffffffffff167fc74777e2214d32e7b4952c2ecbbfc0ec7dc3642e448c3c927261cb69d9f036508a84846040516127f8939291906141ca565b60405180910390a2505050505050505b612810612940565b5050565b61281c612fc2565b73ffffffffffffffffffffffffffffffffffffffff1661283a6109c0565b73ffffffffffffffffffffffffffffffffffffffff16146128995761285d612fc2565b6040517f118cdaa700000000000000000000000000000000000000000000000000000000815260040161289091906131ab565b60405180910390fd5b565b60025f54036128d6576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60025f81905550565b6128e7612fc9565b5f60015f6101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa612929612fc2565b60405161293691906131ab565b60405180910390a1565b60015f81905550565b5f60018054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816001806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b612a13612a6c565b6001805f6101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612a55612fc2565b604051612a6291906131ab565b60405180910390a1565b612a7461090b565b15612aab576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f6040518060a001604052808660ff1681526020018560ff1681526020014263ffffffff168152602001848152602001838152509050600a60ff1686600401805490501015612bd2578560040181908060018154018082558091505060019003905f5260205f2090600302015f909190919091505f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff16021790555060608201518160010155608082015181600201555050600a60ff168660040180549050612bb29190613d94565b866007015f6101000a81548160ff021916908360ff160217905550612cc8565b8086600401876007015f9054906101000a900460ff1660ff1681548110612bfc57612bfb613cbf565b5b905f5260205f2090600302015f820151815f015f6101000a81548160ff021916908360ff1602179055506020820151815f0160016101000a81548160ff021916908360ff1602179055506040820151815f0160026101000a81548163ffffffff021916908363ffffffff1602179055506060820151816001015560808201518160020155905050600a6001876007015f9054906101000a900460ff16612ca2919061442d565b612cac9190614461565b866007015f6101000a81548160ff021916908360ff1602179055505b505050505050565b807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612d2a91906131ab565b602060405180830381865afa158015612d45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612d6991906140b9565b1015612e4557807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff166370a08231846040518263ffffffff1660e01b8152600401612dc991906131ab565b602060405180830381865afa158015612de4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e0891906140b9565b6040517f5f504d20000000000000000000000000000000000000000000000000000000008152600401612e3c929190613bd7565b60405180910390fd5b807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612ea1929190613826565b602060405180830381865afa158015612ebc573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ee091906140b9565b1015612fbe57807f0000000000000000000000003a170c7c987f55c84f28733bfa27962d8cdd5d3b73ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e84306040518363ffffffff1660e01b8152600401612f42929190613826565b602060405180830381865afa158015612f5d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f8191906140b9565b6040517f2a1b2dd8000000000000000000000000000000000000000000000000000000008152600401612fb5929190613bd7565b60405180910390fd5b5050565b5f33905090565b612fd161090b565b613007576040517f8dfc202b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5080545f8255905f5260205f209081019061302491906130e2565b50565b6040518060a001604052805f60ff1681526020015f60ff1681526020015f63ffffffff1681526020015f81526020015f81525090565b828054828255905f5260205f20908101928215613097579160200282015b8281111561309657825182559160200191906001019061307b565b5b5090506130a491906130e2565b5090565b6040518060c001604052805f151581526020015f151581526020015f60ff1681526020015f60ff1681526020015f81526020015f81525090565b5b808211156130f9575f815f9055506001016130e3565b5090565b5f604051905090565b5f80fd5b5f80fd5b5f819050919050565b6131208161310e565b811461312a575f80fd5b50565b5f8135905061313b81613117565b92915050565b5f6020828403121561315657613155613106565b5b5f6131638482850161312d565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6131958261316c565b9050919050565b6131a58161318b565b82525050565b5f6020820190506131be5f83018461319c565b92915050565b6131cd8161318b565b81146131d7575f80fd5b50565b5f813590506131e8816131c4565b92915050565b5f6020828403121561320357613202613106565b5b5f613210848285016131da565b91505092915050565b5f8115159050919050565b61322d81613219565b82525050565b5f6020820190506132465f830184613224565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61329682613250565b810181811067ffffffffffffffff821117156132b5576132b4613260565b5b80604052505050565b5f6132c76130fd565b90506132d3828261328d565b919050565b5f67ffffffffffffffff8211156132f2576132f1613260565b5b602082029050602081019050919050565b5f80fd5b5f613319613314846132d8565b6132be565b9050808382526020820190506020840283018581111561333c5761333b613303565b5b835b818110156133655780613351888261312d565b84526020840193505060208101905061333e565b5050509392505050565b5f82601f8301126133835761338261324c565b5b8135613393848260208601613307565b91505092915050565b5f80604083850312156133b2576133b1613106565b5b5f6133bf8582860161312d565b925050602083013567ffffffffffffffff8111156133e0576133df61310a565b5b6133ec8582860161336f565b9150509250929050565b6133ff8161310e565b82525050565b5f6020820190506134185f8301846133f6565b92915050565b5f60ff82169050919050565b6134338161341e565b82525050565b5f60208201905061344c5f83018461342a565b92915050565b5f610180820190506134665f83018f613224565b613473602083018e613224565b613480604083018d613224565b61348d606083018c61342a565b61349a608083018b6133f6565b6134a760a083018a61342a565b6134b460c08301896133f6565b6134c160e08301886133f6565b6134cf610100830187613224565b6134dd610120830186613224565b6134eb610140830185613224565b6134f96101608301846133f6565b9d9c50505050505050505050505050565b5f60408201905061351d5f830185613224565b61352a6020830184613224565b9392505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6135638161341e565b82525050565b5f63ffffffff82169050919050565b61358181613569565b82525050565b6135908161310e565b82525050565b60a082015f8201516135aa5f85018261355a565b5060208201516135bd602085018261355a565b5060408201516135d06040850182613578565b5060608201516135e36060850182613587565b5060808201516135f66080850182613587565b50505050565b5f6136078383613596565b60a08301905092915050565b5f602082019050919050565b5f61362982613531565b613633818561353b565b935061363e8361354b565b805f5b8381101561366e57815161365588826135fc565b975061366083613613565b925050600181019050613641565b5085935050505092915050565b5f6020820190508181035f830152613693818461361f565b905092915050565b5f819050919050565b5f6136be6136b96136b48461316c565b61369b565b61316c565b9050919050565b5f6136cf826136a4565b9050919050565b5f6136e0826136c5565b9050919050565b6136f0816136d6565b82525050565b5f6020820190506137095f8301846136e7565b92915050565b6137188161341e565b8114613722575f80fd5b50565b5f813590506137338161370f565b92915050565b5f806040838503121561374f5761374e613106565b5b5f61375c85828601613725565b925050602061376d8582860161312d565b9150509250929050565b61378081613219565b82525050565b60c082015f82015161379a5f850182613777565b5060208201516137ad6020850182613777565b5060408201516137c0604085018261355a565b5060608201516137d3606085018261355a565b5060808201516137e66080850182613587565b5060a08201516137f960a0850182613587565b50505050565b5f60e0820190506138125f830185613786565b61381f60c08301846133f6565b9392505050565b5f6040820190506138395f83018561319c565b613846602083018461319c565b9392505050565b5f82825260208201905092915050565b7f496e76616c696420706c617965722061646472657373000000000000000000005f82015250565b5f61389160168361384d565b915061389c8261385d565b602082019050919050565b5f6020820190508181035f8301526138be81613885565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6138fc8261310e565b91506139078361310e565b925082820190508082111561391f5761391e6138c5565b5b92915050565b5f61392f82613569565b915061393a83613569565b9250828201905063ffffffff811115613956576139556138c5565b5b92915050565b7f4e6f206163746976652067616d650000000000000000000000000000000000005f82015250565b5f613990600e8361384d565b915061399b8261395c565b602082019050919050565b5f6020820190508181035f8301526139bd81613984565b9050919050565b7f4e6f2070656e64696e67207265717565737420746f207265636f7665720000005f82015250565b5f6139f8601d8361384d565b9150613a03826139c4565b602082019050919050565b5f6020820190508181035f830152613a25816139ec565b9050919050565b7f52657175657374206a7573742066756c66696c6c65642c206c657420565246205f8201527f636f6d706c657465000000000000000000000000000000000000000000000000602082015250565b5f613a8660288361384d565b9150613a9182613a2c565b604082019050919050565b5f6020820190508181035f830152613ab381613a7a565b9050919050565b7f47616d65206e6f7420656c696769626c6520666f72207265636f7665727920795f8201527f6574000000000000000000000000000000000000000000000000000000000000602082015250565b5f613b1460228361384d565b9150613b1f82613aba565b604082019050919050565b5f6020820190508181035f830152613b4181613b08565b9050919050565b7f4e6f7468696e6720746f20726566756e640000000000000000000000000000005f82015250565b5f613b7c60118361384d565b9150613b8782613b48565b602082019050919050565b5f6020820190508181035f830152613ba981613b70565b9050919050565b5f604082019050613bc35f83018561319c565b613bd060208301846133f6565b9392505050565b5f604082019050613bea5f8301856133f6565b613bf760208301846133f6565b9392505050565b7f47616d65206e6f7420656c696769626c6520666f7220666f7263652073746f705f8201527f2079657400000000000000000000000000000000000000000000000000000000602082015250565b5f613c5860248361384d565b9150613c6382613bfe565b604082019050919050565b5f6020820190508181035f830152613c8581613c4c565b9050919050565b5f613c968261310e565b9150613ca18361310e565b9250828203905081811115613cb957613cb86138c5565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f613cf68261310e565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613d2857613d276138c5565b5b600182019050919050565b5f613d3d8261341e565b9150613d488361341e565b9250828203905060ff811115613d6157613d606138c5565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f613d9e8261310e565b9150613da98361310e565b925082613db957613db8613d67565b5b828206905092915050565b7f42657420616d6f756e742063616e6e6f74206265207a65726f000000000000005f82015250565b5f613df860198361384d565b9150613e0382613dc4565b602082019050919050565b5f6020820190508181035f830152613e2581613dec565b9050919050565b7f42657420616d6f756e7420746f6f206c617267650000000000000000000000005f82015250565b5f613e6060148361384d565b9150613e6b82613e2c565b602082019050919050565b5f6020820190508181035f830152613e8d81613e54565b9050919050565b7f496e76616c69642063686f73656e206e756d62657200000000000000000000005f82015250565b5f613ec860158361384d565b9150613ed382613e94565b602082019050919050565b5f6020820190508181035f830152613ef581613ebc565b9050919050565b7f557365722068617320616e206163746976652067616d650000000000000000005f82015250565b5f613f3060178361384d565b9150613f3b82613efc565b602082019050919050565b5f6020820190508181035f830152613f5d81613f24565b9050919050565b7f557365722068617320612070656e64696e6720726571756573740000000000005f82015250565b5f613f98601a8361384d565b9150613fa382613f64565b602082019050919050565b5f6020820190508181035f830152613fc581613f8c565b9050919050565b5f613fd68261310e565b9150613fe18361310e565b9250828202613fef8161310e565b91508282048414831517614006576140056138c5565b5b5092915050565b5f6140178261310e565b91506140228361310e565b92508261403257614031613d67565b5b828204905092915050565b7f5061796f75742063616c63756c6174696f6e206f766572666c6f7700000000005f82015250565b5f614071601b8361384d565b915061407c8261403d565b602082019050919050565b5f6020820190508181035f83015261409e81614065565b9050919050565b5f815190506140b381613117565b92915050565b5f602082840312156140ce576140cd613106565b5b5f6140db848285016140a5565b91505092915050565b5f819050919050565b6140f6816140e4565b82525050565b5f67ffffffffffffffff82169050919050565b614118816140fc565b82525050565b5f61ffff82169050919050565b6141348161411e565b82525050565b61414381613569565b82525050565b5f61416361415e6141598461341e565b61369b565b613569565b9050919050565b61417381614149565b82525050565b5f60a08201905061418c5f8301886140ed565b614199602083018761410f565b6141a6604083018661412b565b6141b3606083018561413a565b6141c0608083018461416a565b9695505050505050565b5f6060820190506141dd5f8301866133f6565b6141ea602083018561342a565b6141f760408301846133f6565b949350505050565b7f52657175657374206e6f7420666f756e640000000000000000000000000000005f82015250565b5f61423360118361384d565b915061423e826141ff565b602082019050919050565b5f6020820190508181035f83015261426081614227565b9050919050565b7f5265717565737420616c72656164792066756c66696c6c6564000000000000005f82015250565b5f61429b60198361384d565b91506142a682614267565b602082019050919050565b5f6020820190508181035f8301526142c88161428f565b9050919050565b7f496e76616c69642072616e646f6d20776f726473206c656e67746800000000005f82015250565b5f614303601b8361384d565b915061430e826142cf565b602082019050919050565b5f6020820190508181035f830152614330816142f7565b9050919050565b7f52657175657374204944206d69736d61746368000000000000000000000000005f82015250565b5f61436b60138361384d565b915061437682614337565b602082019050919050565b5f6020820190508181035f8301526143988161435f565b9050919050565b7f42657420616d6f756e7420746f6f206c6172676520666f72207061796f7574205f8201527f63616c63756c6174696f6e000000000000000000000000000000000000000000602082015250565b5f6143f9602b8361384d565b91506144048261439f565b604082019050919050565b5f6020820190508181035f830152614426816143ed565b9050919050565b5f6144378261341e565b91506144428361341e565b9250828201905060ff81111561445b5761445a6138c5565b5b92915050565b5f61446b8261341e565b91506144768361341e565b92508261448657614485613d67565b5b82820690509291505056fea26469706673582212202f17750668dcdefc304ba4a04de6d2e7ac58b20582b56c65fc4d5d4fcb4c7f5a64736f6c63430008140033
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
88951:22850:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;106955:132;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;107241:204;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;54809:261;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;90171:60;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90463:48;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;104713:80;;;:::i;:::-;;90238:65;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;107977:2042;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;90518:44;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90820:33;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90743:31;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;6146:86;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;107594:203;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;9519:103;;;:::i;:::-;;104568:76;;;:::i;:::-;;8844:87;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90121:43;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;91316:51;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;99507:2542;;;:::i;:::-;;102168:2332;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;90071:43;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;105527:1277;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90621:33;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;90781:32;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;9777:220;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;93220:2943;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;104989:381;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;106955:132;107026:7;107053:15;:26;107069:9;107053:26;;;;;;;;;;;;;;;;;;;;;107046:33;;106955:132;;;:::o;107241:204::-;107307:4;107324:21;107348:8;:16;107357:6;107348:16;;;;;;;;;;;;;;;107324:40;;107382:4;:16;;:25;;;;;;;;;;;;:55;;;;;107436:1;107411:4;:21;;;:26;;107382:55;107375:62;;;107241:204;;;:::o;54809:261::-;54923:14;54909:28;;:10;:28;;;54905:111;;54981:10;54993:14;54955:53;;;;;;;;;;;;:::i;:::-;;;;;;;;54905:111;55022:42;55041:9;55052:11;55022:18;:42::i;:::-;54809:261;;:::o;90171:60::-;90212:19;90171:60;:::o;90463:48::-;90508:3;90463:48;:::o;104713:80::-;8730:13;:11;:13::i;:::-;2446:21:::1;:19;:21::i;:::-;104775:10:::2;:8;:10::i;:::-;2490:20:::1;:18;:20::i;:::-;104713:80::o:0;90238:65::-;90284:19;90238:65;:::o;107977:2042::-;108049:13;108073:10;108094:16;108121:18;108150:14;108175:12;108198:14;108223:17;108251:18;108280:21;108312;108344:25;108410:1;108392:20;;:6;:20;;;108388:79;;108421:46;;;;;;;;;;:::i;:::-;;;;;;;;108388:79;108488:21;108512:8;:16;108521:6;108512:16;;;;;;;;;;;;;;;108488:40;;108560:4;:16;;:25;;;;;;;;;;;;108549:36;;108610:4;:16;;:26;;;;;;;;;;;;108596:40;;108662:4;:16;;:29;;;;;;;;;;;;108647:44;;108711:4;:16;;:23;;;108702:32;;108754:4;:16;;:23;;;;;;;;;;;;108745:32;;108797:4;:16;;:23;;;108788:32;;108843:4;:21;;;108831:33;;108895:4;:24;;;;;;;;;;;;108875:44;;;;109013:1;109004:6;:10;:24;;;;;109027:1;109018:6;:10;;;109004:24;:48;;;;;90063:1;109032:20;;:6;:20;;;;109004:48;108996:56;;109089:5;109073:21;;109124:5;109105:24;;109215:1;109202:9;:14;109198:196;;109233:29;109265:10;:21;109276:9;109265:21;;;;;;;;;;;109233:53;;109317:7;:14;;;;;;;;;;;;109301:30;;109365:7;:17;;;;;;;;;;;;109346:36;;109218:176;109198:196;109476:5;109457:24;;109496:8;109492:520;;;109589:28;90417:3;109635:4;:20;;;:38;;;;:::i;:::-;109620:12;:53;109589:84;;109688:21;90360:7;109730:4;:24;;;;;;;;;;;;:39;;;;:::i;:::-;109712:57;;:15;:57;109688:81;;109784:18;109818:1;109805:9;:14;;:31;;;;;109823:13;109805:31;109784:52;;109940:23;:43;;;;;109967:16;109940:43;:60;;;;;109987:13;109940:60;109921:79;;109506:506;;;109492:520;108377:1642;107977:2042;;;;;;;;;;;;;:::o;90518:44::-;90559:3;90518:44;:::o;90820:33::-;;;;:::o;90743:31::-;;;;:::o;6146:86::-;6193:4;6217:7;;;;;;;;;;;6210:14;;6146:86;:::o;107594:203::-;107658:4;107675:21;107699:8;:16;107708:6;107699:16;;;;;;;;;;;;;;;107675:40;;107734:4;:16;;:25;;;;;;;;;;;;107733:26;:56;;;;;107788:1;107763:4;:21;;;:26;107733:56;107726:63;;;107594:203;;;:::o;9519:103::-;8730:13;:11;:13::i;:::-;9584:30:::1;9611:1;9584:18;:30::i;:::-;9519:103::o:0;104568:76::-;8730:13;:11;:13::i;:::-;2446:21:::1;:19;:21::i;:::-;104628:8:::2;:6;:8::i;:::-;2490:20:::1;:18;:20::i;:::-;104568:76::o:0;8844:87::-;8890:7;8917:6;;;;;;;;;;8910:13;;8844:87;:::o;90121:43::-;90159:5;90121:43;:::o;91316:51::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;99507:2542::-;2446:21;:19;:21::i;:::-;5751:19:::1;:17;:19::i;:::-;99585:21:::2;99609:8;:20;99618:10;99609:20;;;;;;;;;;;;;;;99585:44;;99730:4;:16;;:25;;;;;;;;;;;;99725:66;;99764:27;;;;;;;;;;:::i;:::-;;;;;;;;99725:66;99812:17;99832:4;:21;;;99812:41;;99945:1;99932:9;:14:::0;99928:96:::2;;99970:42;;;;;;;;;;:::i;:::-;;;;;;;;99928:96;100101:10;:21;100112:9;100101:21;;;;;;;;;;;:31;;;;;;;;;;;;:92;;;;;100190:2;100167:4;:20;;;:25;;;;:::i;:::-;100151:12;:41;;100101:92;100097:185;;;100217:53;;;;;;;;;;:::i;:::-;;;;;;;;100097:185;100364:28;90417:3;100410:4;:20;;;:38;;;;:::i;:::-;100395:12;:53;100364:84;;100459:21;90360:7;100501:4;:24;;;;;;;;;;;;:39;;;;:::i;:::-;100483:57;;:15;:57;100459:81;;100645:18;100679:1:::0;100666:9:::2;:14;;:46;;;;;100684:10;:21;100695:9;100684:21;;;;;;;;;;;:28;;;;;;;;;;;;100666:46;100645:67;;100793:23;100792:24;:45;;;;100821:16;100820:17;100792:45;:63;;;;100842:13;100841:14;100792:63;100788:150;;;100879:47;;;;;;;;;;:::i;:::-;;;;;;;;100788:150;101021:20;101044:4;:16;;:23;;;101021:46;;101108:1;101092:12;:17:::0;101088:60:::2;;101118:30;;;;;;;;;;:::i;:::-;;;;;;;;101088:60;101210:10;:21;101221:9;101210:21;;;;;;;;;;;;101203:28:::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;101249:15;:26;101265:9;101249:26;;;;;;;;;;;;101242:33;;;;;;;;;;;101293:16;:27;101310:9;101293:27;;;;;;;;;;;;101286:34;;;;;;;;;;;101400:4;101371;:16;;:26;;;:33;;;;;;;;;;;;;;;;;;101443:5;101415:4;:16;;:25;;;:33;;;;;;;;;;;;;;;;;;90559:3;101459:4;:16;;:23;;;:42;;;;;;;;;;;;;;;;;;101538:12;101512:4;:16;;:23;;:38;;;;101595:1;101571:4;:21;;:25;;;;101631:5;101607:4;:21;;;:29;;;;;;;;;;;;;;;;;;101712:9;:14;;;101727:10;101739:12;101712:40;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;101796:177;101829:4;101848;:16;;:29;;;;;;;;;;;;90559:3;101923:12;101950;101796:18;:177::i;:::-;102005:10;101991:50;;;102017:9;102028:12;101991:50;;;;;;;:::i;:::-;;;;;;;;99574:2475;;;;;;2490:20:::0;:18;:20::i;:::-;99507:2542::o;102168:2332::-;8730:13;:11;:13::i;:::-;2446:21:::1;:19;:21::i;:::-;102250::::2;102274:8;:16;102283:6;102274:16;;;;;;;;;;;;;;;102250:40;;102347:4;:16;;:25;;;;;;;;;;;;102342:66;;102381:27;;;;;;;;;;:::i;:::-;;;;;;;;102342:66;102421:17;102441:4;:21;;;102421:41;;102553:1;102540:9;:14;;:49;;;;;102558:10;:21;102569:9;102558:21;;;;;;;;;;;:31;;;;;;;;;;;;102540:49;:110;;;;;102647:2;102624:4;:20;;;:25;;;;:::i;:::-;102608:12;:41;;102540:110;102536:203;;;102674:53;;;;;;;;;;:::i;:::-;;;;;;;;102536:203;102813:28;90417:3;102859:4;:20;;;:38;;;;:::i;:::-;102844:12;:53;102813:84;;102908:21;90360:7;102950:4;:24;;;;;;;;;;;;:39;;;;:::i;:::-;102932:57;;:15;:57;102908:81;;103094:18;103128:1:::0;103115:9:::2;:14;;:46;;;;;103133:10;:21;103144:9;103133:21;;;;;;;;;;;:28;;;;;;;;;;;;103115:46;103094:67;;103242:23;103241:24;:45;;;;103270:16;103269:17;103241:45;:63;;;;103291:13;103290:14;103241:63;103237:152;;;103328:49;;;;;;;;;;:::i;:::-;;;;;;;;103237:152;103401:20;103424:4;:16;;:23;;;103401:46;;103488:1;103472:12;:17:::0;103468:60:::2;;103498:30;;;;;;;;;;:::i;:::-;;;;;;;;103468:60;103624:1;103611:9;:14;103607:172;;103649:15;:26;103665:9;103649:26;;;;;;;;;;;;103642:33;;;;;;;;;;;103697:16;:27;103714:9;103697:27;;;;;;;;;;;;103690:34;;;;;;;;;;;103746:10;:21;103757:9;103746:21;;;;;;;;;;;;103739:28:::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;103607:172;103855:4;103826;:16;;:26;;;:33;;;;;;;;;;;;;;;;;;103898:5;103870:4;:16;;:25;;;:33;;;;;;;;;;;;;;;;;;90508:3;103914:4;:16;;:23;;;:46;;;;;;;;;;;;;;;;;;103997:12;103971:4;:16;;:23;;:38;;;;104044:1;104020:4;:21;;:25;;;;104080:5;104056:4;:21;;;:29;;;;;;;;;;;;;;;;;;104161:9;:14;;;104176:6;104184:12;104161:36;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;104247:181;104280:4;104299;:16;;:29;;;;;;;;;;;;90508:3;104378:12;104405;104247:18;:181::i;:::-;104460:6;104446:46;;;104468:9;104479:12;104446:46;;;;;;;:::i;:::-;;;;;;;;102239:2261;;;;;;2490:20:::1;:18;:20::i;:::-;102168:2332:::0;:::o;90071:43::-;90112:2;90071:43;:::o;105527:1277::-;105589:19;105643:1;105625:20;;:6;:20;;;105621:79;;105654:46;;;;;;;;;;:::i;:::-;;;;;;;;105621:79;105721:21;105745:8;:16;105754:6;105745:16;;;;;;;;;;;;;;;105721:40;;105772:14;105789:4;:15;;:22;;;;105772:39;;105846:1;105836:6;:11;105832:43;;105873:1;105856:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;105849:26;;;;;;105832:43;105944:20;90112:2;105967:25;;:6;:25;:53;;106014:6;105967:53;;;90112:2;105967:53;;;105944:76;;106031:31;106082:12;106065:30;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;106031:64;;90112:2;106157:25;;:6;:25;106153:605;;;106261:9;106256:120;106280:6;106276:1;:10;106256:120;;;106329:4;:15;;106358:1;106354;106345:6;:10;;;;:::i;:::-;:14;;;;:::i;:::-;106329:31;;;;;;;;:::i;:::-;;;;;;;;;;;;106312:48;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;106324:1;106312:14;;;;;;;;:::i;:::-;;;;;;;:48;;;;106288:3;;;;;:::i;:::-;;;;106256:120;;;;106153:605;;;106456:19;106499:1;106478:4;:17;;;;;;;;;;;;:22;;;:69;;106546:1;106526:4;:17;;;;;;;;;;;;:21;;;;:::i;:::-;106478:69;;;106522:1;90112:2;106503:20;;;;:::i;:::-;106478:69;106456:91;;;;106581:9;106576:171;90112:2;106596:20;;:1;:20;106576:171;;;106659:4;:15;;90112:2;106675:55;;106709:1;90112:2;106676:30;;:11;:30;;;;:::i;:::-;:34;;;;:::i;:::-;106675:55;;;;:::i;:::-;106659:72;;;;;;;;:::i;:::-;;;;;;;;;;;;106642:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;106654:1;106642:14;;;;;;;;:::i;:::-;;;;;;;:89;;;;106618:3;;;;;:::i;:::-;;;;106576:171;;;;106393:365;106153:605;106785:11;106778:18;;;;;;105527:1277;;;;:::o;90621:33::-;;;:::o;90781:32::-;;;;:::o;9777:220::-;8730:13;:11;:13::i;:::-;9882:1:::1;9862:22;;:8;:22;;::::0;9858:93:::1;;9936:1;9908:31;;;;;;;;;;;:::i;:::-;;;;;;;;9858:93;9961:28;9980:8;9961:18;:28::i;:::-;9777:220:::0;:::o;93220:2943::-;93319:17;2446:21;:19;:21::i;:::-;5751:19:::1;:17;:19::i;:::-;93432:1:::2;93422:6;:11:::0;93418:73:::2;;93442:49;;;;;;;;;;:::i;:::-;;;;;;;;93418:73;90212:19;93506:6;:23;93502:80;;;93538:44;;;;;;;;;;:::i;:::-;;;;;;;;93502:80;93612:1;93597:12;:16;;;:45;;;;90063:1;93617:25;;:12;:25;;;93597:45;93593:103;;;93651:45;;;;;;;;;;:::i;:::-;;;;;;;;93593:103;93757:21;93781:8;:20;93790:10;93781:20;;;;;;;;;;;;;;;93757:44;;93816:4;:16;;:25;;;;;;;;;;;;93812:74;;;93850:36;;;;;;;;;;:::i;:::-;;;;;;;;93812:74;93926:1;93901:4;:21;;;:26;93897:78;;93936:39;;;;;;;;;;:::i;:::-;;;;;;;;93897:78;94022:47;94050:10;94062:6;94022:27;:47::i;:::-;94121:23;94156:1;94147:6;:10;;;;:::i;:::-;94121:36;;94195:6;94190:1;94172:15;:19;;;;:::i;:::-;:29;94168:95;;94210:53;;;;;;;;;;:::i;:::-;;;;;;;;94168:95;90284:19;94278:15;:37;94274:132;;;94357:15;90284:19;94339:55;;;;;;;;;;;;:::i;:::-;;;;;;;;94274:132;94508:25;94536:9;:30;;;:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;94508:60;;94601:17;94583:15;:35;94579:128;;;94660:15;94677:17;94642:53;;;;;;;;;;;;:::i;:::-;;;;;;;;94579:128;94784:9;:18;;;94803:10;94815:6;94784:38;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;94897:6;94875:18;;:28;;;;;;;:::i;:::-;;;;;;;;94975:11;:30;;;95020:9;95044:16;95075:20;95110:16;95141:8;94975:185;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;94963:197;;95231:128;;;;;;;;95342:5;95231:128;;;;;;95312:4;95231:128;;;;;;95287:1;95273:16;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;95231:128;;::::0;95207:10:::2;:21;95218:9;95207:21;;;;;;;;;;;:152;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;95446:10;95417:15;:26;95433:9;95417:26;;;;;;;;;;;;:39;;;;;;;;;;;;;;;;;;95497:4;95467:16;:27;95484:9;95467:27;;;;;;;;;;;;:34;;;;;;;;;;;;;;;;;;95602:15;95568:4;:24;;;:50;;;;;;;;;;;;;;;;;;95652:12;95629:4;:20;;:35;;;;95699:5;95675:4;:21;;;:29;;;;;;;;;;;;;;;;;;95784:200;;;;;;;;95819:4;95784:200;;;;;;95849:5;95784:200;;;;;;95883:12;95784:200;;;;;;95918:1;95784:200;;;;;;95942:6;95784:200;;;;95971:1;95784:200;;::::0;95765:4:::2;:16;;:219;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;96029:9;96005:4;:21;;:33;;;;96074:10;96064:54;;;96086:9;96097:12;96111:6;96064:54;;;;;;;;:::i;:::-;;;;;;;;96139:16;;;2490:20:::0;:18;:20::i;:::-;93220:2943;;;;:::o;104989:381::-;105059:26;;:::i;:::-;105096:18;105155:1;105137:20;;:6;:20;;;105133:79;;105166:46;;;;;;;;;;:::i;:::-;;;;;;;;105133:79;105223:21;105247:8;:16;105256:6;105247:16;;;;;;;;;;;;;;;105223:40;;105296:4;:16;;105327:4;:24;;;;;;;;;;;;105274:88;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;104989:381;;;:::o;96338:3077::-;2446:21;:19;:21::i;:::-;96524:29:::1;96556:10;:21;96567:9;96556:21;;;;;;;;;;;96524:53;;96593:7;:14;;;;;;;;;;;;96588:57;;96616:29;;;;;;;;;;:::i;:::-;;;;;;;;96588:57;96660:7;:17;;;;;;;;;;;;96656:67;;;96686:37;;;;;;;;;;:::i;:::-;;;;;;;;96656:67;96760:8;96738:30;;:11;:18;:30;96734:82;;96777:39;;;;;;;;;;:::i;:::-;;;;;;;;96734:82;96875:14;96892:15;:26;96908:9;96892:26;;;;;;;;;;;;;;;;;;;;;96875:43;;96951:1;96933:20;;:6;:20;;::::0;96929:67:::1;;96962:34;;;;;;;;;;:::i;:::-;;;;;;;;96929:67;97017:21;97041:8;:16;97050:6;97041:16;;;;;;;;;;;;;;;97017:40;;97097:9;97072:4;:21;;;:34;97068:79;;97115:32;;;;;;;;;;:::i;:::-;;;;;;;;97068:79;97253:4;97233:7;:17;;;:24;;;;;;;;;;;;;;;;;;97290:11;97268:7;:19;;:33;;;;;;;;;;;;:::i;:::-;;97336:4;97312;:21;;;:28;;;;;;;;;;;;;;;;;;97408:4;:16;;:25;;;;;;;;;;;;97403:372;;97540:10;:21;97551:9;97540:21;;;;;;;;;;;;97533:28:::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;97583:15;:26;97599:9;97583:26;;;;;;;;;;;;97576:33;;;;;;;;;;;97631:16;:27;97648:9;97631:27;;;;;;;;;;;;97624:34;;;;;;;;;;;97697:1;97673:4;:21;;:25;;;;97737:5;97713:4;:21;;;:29;;;;;;;;;;;;;;;;;;97757:7;;;;;97403:372;97822:18;97843:4;:16;;:29;;;;;;;;;;;;97822:50;;97883:17;97903:4;:16;;:23;;;97883:43;;98011:12;98062:1;90063;98032:27;;:11;98044:1;98032:14;;;;;;;;:::i;:::-;;;;;;;;:27;;;;:::i;:::-;:31;;;;:::i;:::-;98011:53;;98117:14;98166:6;98150:22;;:12;:22;;::::0;98146:284:::1;;98268:1;98248:17;:21;;;;:::i;:::-;98236:9;:33;98232:150;;;98297:69;;;;;;;;;;:::i;:::-;;;;;;;;98232:150;98417:1;98405:9;:13;;;;:::i;:::-;98396:22;;98146:284;98501:6;98475:4;:16;;:23;;;:32;;;;;;;;;;;;;;;;;;98546:5;98518:4;:16;;:25;;;:33;;;;;;;;;;;;;;;;;;98591:4;98562;:16;;:26;;;:33;;;;;;;;;;;;;;;;;;98632:6;98606:4;:16;;:23;;:32;;;;98686:141;98719:4;98738:12;98765:6;98786:9;98810:6;98686:18;:141::i;:::-;98894:16;;98892:18;;;;;;;;;;;98936:1;98927:6;:10;98923:70;;;98975:6;98954:17;;:27;;;;;;;:::i;:::-;;;;;;;;98923:70;99032:15;:26;99048:9;99032:26;;;;;;;;;;;;99025:33;;;;;;;;;;;99076:16;:27;99093:9;99076:27;;;;;;;;;;;;99069:34;;;;;;;;;;;99121:10;:21;99132:9;99121:21;;;;;;;;;;;;99114:28:::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;99177:1;99153:4;:21;;:25;;;;99282:1;99273:6;:10;99269:73;;;99300:9;:14;;;99315:6;99323;99300:30;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;99269:73;99373:6;99359:48;;;99381:9;99392:6;99400;99359:48;;;;;;;;:::i;:::-;;;;;;;;96446:2969;;;;;;;2478:1;2490:20:::0;:18;:20::i;:::-;96338:3077;;:::o;9009:166::-;9080:12;:10;:12::i;:::-;9069:23;;:7;:5;:7::i;:::-;:23;;;9065:103;;9143:12;:10;:12::i;:::-;9116:40;;;;;;;;;;;:::i;:::-;;;;;;;;9065:103;9009:166::o;2526:315::-;1824:1;2655:7;;:18;2651:88;;2697:30;;;;;;;;;;;;;;2651:88;1824:1;2816:7;:17;;;;2526:315::o;7047:120::-;6010:16;:14;:16::i;:::-;7116:5:::1;7106:7;;:15;;;;;;;;;;;;;;;;;;7137:22;7146:12;:10;:12::i;:::-;7137:22;;;;;;:::i;:::-;;;;;;;;7047:120::o:0;2849:212::-;1781:1;3032:7;:21;;;;2849:212::o;10157:191::-;10231:16;10250:6;;;;;;;;;;10231:25;;10276:8;10267:6;;:17;;;;;;;;;;;;;;;;;;10331:8;10300:40;;10321:8;10300:40;;;;;;;;;;;;10220:128;10157:191;:::o;6788:118::-;5751:19;:17;:19::i;:::-;6858:4:::1;6848:7:::0;::::1;:14;;;;;;;;;;;;;;;;;;6878:20;6885:12;:10;:12::i;:::-;6878:20;;;;;;:::i;:::-;;;;;;;;6788:118::o:0;6305:132::-;6371:8;:6;:8::i;:::-;6367:63;;;6403:15;;;;;;;;;;;;;;6367:63;6305:132::o;110925:873::-;111113:24;111140:206;;;;;;;;111180:12;111140:206;;;;;;111221:6;111140:206;;;;;;111289:15;111140:206;;;;;;111250:6;111140:206;;;;111328:6;111140:206;;;111113:233;;90112:2;111363:41;;:4;:15;;:22;;;;:41;111359:432;;;111464:4;:15;;111485:6;111464:28;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;90112:2;111533:41;;:4;:15;;:22;;;;:41;;;;:::i;:::-;111507:4;:17;;;:68;;;;;;;;;;;;;;;;;;111359:432;;;111696:6;111659:4;:15;;111675:4;:17;;;;;;;;;;;;111659:34;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:43;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;90112:2;111758:1;111738:4;:17;;;;;;;;;;;;:21;;;;:::i;:::-;111737:42;;;;:::i;:::-;111717:4;:17;;;:62;;;;;;;;;;;;;;;;;;111359:432;111102:696;110925:873;;;;;:::o;110229:414::-;110356:6;110326:9;:19;;;110346:6;110326:27;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:36;110322:136;;;110410:6;110418:9;:19;;;110438:6;110418:27;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;110386:60;;;;;;;;;;;;:::i;:::-;;;;;;;;110322:136;110519:6;110474:9;:19;;;110494:6;110510:4;110474:42;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:51;110470:164;;;110571:6;110579:9;:19;;;110599:6;110615:4;110579:42;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;110549:73;;;;;;;;;;;;:::i;:::-;;;;;;;;110470:164;110229:414;;:::o;4056:98::-;4109:7;4136:10;4129:17;;4056:98;:::o;6514:130::-;6578:8;:6;:8::i;:::-;6573:64;;6610:15;;;;;;;;;;;;;;6573:64;6514: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:117::-;2755:1;2752;2745:12;2769:102;2810:6;2861:2;2857:7;2852:2;2845:5;2841:14;2837:28;2827:38;;2769:102;;;:::o;2877:180::-;2925:77;2922:1;2915:88;3022:4;3019:1;3012:15;3046:4;3043:1;3036:15;3063:281;3146:27;3168:4;3146:27;:::i;:::-;3138:6;3134:40;3276:6;3264:10;3261:22;3240:18;3228:10;3225:34;3222:62;3219:88;;;3287:18;;:::i;:::-;3219:88;3327:10;3323:2;3316:22;3106:238;3063:281;;:::o;3350:129::-;3384:6;3411:20;;:::i;:::-;3401:30;;3440:33;3468:4;3460:6;3440:33;:::i;:::-;3350:129;;;:::o;3485:311::-;3562:4;3652:18;3644:6;3641:30;3638:56;;;3674:18;;:::i;:::-;3638:56;3724:4;3716:6;3712:17;3704:25;;3784:4;3778;3774:15;3766:23;;3485:311;;;:::o;3802:117::-;3911:1;3908;3901:12;3942:710;4038:5;4063:81;4079:64;4136:6;4079:64;:::i;:::-;4063:81;:::i;:::-;4054:90;;4164:5;4193:6;4186:5;4179:21;4227:4;4220:5;4216:16;4209:23;;4280:4;4272:6;4268:17;4260:6;4256:30;4309:3;4301:6;4298:15;4295:122;;;4328:79;;:::i;:::-;4295:122;4443:6;4426:220;4460:6;4455:3;4452:15;4426:220;;;4535:3;4564:37;4597:3;4585:10;4564:37;:::i;:::-;4559:3;4552:50;4631:4;4626:3;4622:14;4615:21;;4502:144;4486:4;4481:3;4477:14;4470:21;;4426:220;;;4430:21;4044:608;;3942:710;;;;;:::o;4675:370::-;4746:5;4795:3;4788:4;4780:6;4776:17;4772:27;4762:122;;4803:79;;:::i;:::-;4762:122;4920:6;4907:20;4945:94;5035:3;5027:6;5020:4;5012:6;5008:17;4945:94;:::i;:::-;4936:103;;4752:293;4675:370;;;;:::o;5051:684::-;5144:6;5152;5201:2;5189:9;5180:7;5176:23;5172:32;5169:119;;;5207:79;;:::i;:::-;5169:119;5327:1;5352:53;5397:7;5388:6;5377:9;5373:22;5352:53;:::i;:::-;5342:63;;5298:117;5482:2;5471:9;5467:18;5454:32;5513:18;5505:6;5502:30;5499:117;;;5535:79;;:::i;:::-;5499:117;5640:78;5710:7;5701:6;5690:9;5686:22;5640:78;:::i;:::-;5630:88;;5425:303;5051:684;;;;;:::o;5741:118::-;5828:24;5846:5;5828:24;:::i;:::-;5823:3;5816:37;5741:118;;:::o;5865:222::-;5958:4;5996:2;5985:9;5981:18;5973:26;;6009:71;6077:1;6066:9;6062:17;6053:6;6009:71;:::i;:::-;5865:222;;;;:::o;6093:86::-;6128:7;6168:4;6161:5;6157:16;6146:27;;6093:86;;;:::o;6185:112::-;6268:22;6284:5;6268:22;:::i;:::-;6263:3;6256:35;6185:112;;:::o;6303:214::-;6392:4;6430:2;6419:9;6415:18;6407:26;;6443:67;6507:1;6496:9;6492:17;6483:6;6443:67;:::i;:::-;6303:214;;;;:::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:1040::-;9353:4;9348:3;9344:14;9448:4;9441:5;9437:16;9431:23;9467:59;9520:4;9515:3;9511:14;9497:12;9467:59;:::i;:::-;9368:168;9626:4;9619:5;9615:16;9609:23;9645:59;9698:4;9693:3;9689:14;9675:12;9645:59;:::i;:::-;9546:168;9801:4;9794:5;9790:16;9784:23;9820:61;9875:4;9870:3;9866:14;9852:12;9820:61;:::i;:::-;9724:167;9975:4;9968:5;9964:16;9958:23;9994:63;10051:4;10046:3;10042:14;10028:12;9994:63;:::i;:::-;9901:166;10151:4;10144:5;10140:16;10134:23;10170:63;10227:4;10222:3;10218:14;10204:12;10170:63;:::i;:::-;10077:166;9322:928;9210:1040;;:::o;10256:291::-;10381:10;10402:102;10500:3;10492:6;10402:102;:::i;:::-;10536:4;10531:3;10527:14;10513:28;;10256:291;;;;:::o;10553:141::-;10651:4;10683;10678:3;10674:14;10666:22;;10553:141;;;:::o;10750:956::-;10925:3;10954:82;11030:5;10954:82;:::i;:::-;11052:114;11159:6;11154:3;11052:114;:::i;:::-;11045:121;;11190:84;11268:5;11190:84;:::i;:::-;11297:7;11328:1;11313:368;11338:6;11335:1;11332:13;11313:368;;;11414:6;11408:13;11441:119;11556:3;11541:13;11441:119;:::i;:::-;11434:126;;11583:88;11664:6;11583:88;:::i;:::-;11573:98;;11373:308;11360:1;11357;11353:9;11348:14;;11313:368;;;11317:14;11697:3;11690:10;;10930:776;;;10750:956;;;;:::o;11712:485::-;11911:4;11949:2;11938:9;11934:18;11926:26;;11998:9;11992:4;11988:20;11984:1;11973:9;11969:17;11962:47;12026:164;12185:4;12176:6;12026:164;:::i;:::-;12018:172;;11712:485;;;;:::o;12203:60::-;12231:3;12252:5;12245:12;;12203:60;;;:::o;12269:142::-;12319:9;12352:53;12370:34;12379:24;12397:5;12379:24;:::i;:::-;12370:34;:::i;:::-;12352:53;:::i;:::-;12339:66;;12269:142;;;:::o;12417:126::-;12467:9;12500:37;12531:5;12500:37;:::i;:::-;12487:50;;12417:126;;;:::o;12549:141::-;12614:9;12647:37;12678:5;12647:37;:::i;:::-;12634:50;;12549:141;;;:::o;12696:161::-;12798:52;12844:5;12798:52;:::i;:::-;12793:3;12786:65;12696:161;;:::o;12863:252::-;12971:4;13009:2;12998:9;12994:18;12986:26;;13022:86;13105:1;13094:9;13090:17;13081:6;13022:86;:::i;:::-;12863:252;;;;:::o;13121:118::-;13192:22;13208:5;13192:22;:::i;:::-;13185:5;13182:33;13172:61;;13229:1;13226;13219:12;13172:61;13121:118;:::o;13245:135::-;13289:5;13327:6;13314:20;13305:29;;13343:31;13368:5;13343:31;:::i;:::-;13245:135;;;;:::o;13386:470::-;13452:6;13460;13509:2;13497:9;13488:7;13484:23;13480:32;13477:119;;;13515:79;;:::i;:::-;13477:119;13635:1;13660:51;13703:7;13694:6;13683:9;13679:22;13660:51;:::i;:::-;13650:61;;13606:115;13760:2;13786:53;13831:7;13822:6;13811:9;13807:22;13786:53;:::i;:::-;13776:63;;13731:118;13386:470;;;;;:::o;13862:99::-;13933:21;13948:5;13933:21;:::i;:::-;13928:3;13921:34;13862:99;;:::o;14011:1210::-;14162:4;14157:3;14153:14;14253:4;14246:5;14242:16;14236:23;14272:57;14323:4;14318:3;14314:14;14300:12;14272:57;:::i;:::-;14177:162;14426:4;14419:5;14415:16;14409:23;14445:57;14496:4;14491:3;14487:14;14473:12;14445:57;:::i;:::-;14349:163;14602:4;14595:5;14591:16;14585:23;14621:59;14674:4;14669:3;14665:14;14651:12;14621:59;:::i;:::-;14522:168;14774:4;14767:5;14763:16;14757:23;14793:59;14846:4;14841:3;14837:14;14823:12;14793:59;:::i;:::-;14700:162;14946:4;14939:5;14935:16;14929:23;14965:63;15022:4;15017:3;15013:14;14999:12;14965:63;:::i;:::-;14872:166;15122:4;15115:5;15111:16;15105:23;15141:63;15198:4;15193:3;15189:14;15175:12;15141:63;:::i;:::-;15048:166;14131:1090;14011:1210;;:::o;15227:442::-;15402:4;15440:3;15429:9;15425:19;15417:27;;15454:125;15576:1;15565:9;15561:17;15552:6;15454:125;:::i;:::-;15589:73;15657:3;15646:9;15642:19;15633:6;15589:73;:::i;:::-;15227:442;;;;;:::o;15675:332::-;15796:4;15834:2;15823:9;15819:18;15811:26;;15847:71;15915:1;15904:9;15900:17;15891:6;15847:71;:::i;:::-;15928:72;15996:2;15985:9;15981:18;15972:6;15928:72;:::i;:::-;15675:332;;;;;:::o;16013:169::-;16097:11;16131:6;16126:3;16119:19;16171:4;16166:3;16162:14;16147:29;;16013:169;;;;:::o;16188:172::-;16328:24;16324:1;16316:6;16312:14;16305:48;16188:172;:::o;16366:366::-;16508:3;16529:67;16593:2;16588:3;16529:67;:::i;:::-;16522:74;;16605:93;16694:3;16605:93;:::i;:::-;16723:2;16718:3;16714:12;16707:19;;16366:366;;;:::o;16738:419::-;16904:4;16942:2;16931:9;16927:18;16919:26;;16991:9;16985:4;16981:20;16977:1;16966:9;16962:17;16955:47;17019:131;17145:4;17019:131;:::i;:::-;17011:139;;16738:419;;;:::o;17163:180::-;17211:77;17208:1;17201:88;17308:4;17305:1;17298:15;17332:4;17329:1;17322:15;17349:191;17389:3;17408:20;17426:1;17408:20;:::i;:::-;17403:25;;17442:20;17460:1;17442:20;:::i;:::-;17437:25;;17485:1;17482;17478:9;17471:16;;17506:3;17503:1;17500:10;17497:36;;;17513:18;;:::i;:::-;17497:36;17349:191;;;;:::o;17546:197::-;17585:3;17604:19;17621:1;17604:19;:::i;:::-;17599:24;;17637:19;17654:1;17637:19;:::i;:::-;17632:24;;17679:1;17676;17672:9;17665:16;;17702:10;17697:3;17694:19;17691:45;;;17716:18;;:::i;:::-;17691:45;17546:197;;;;:::o;17749:164::-;17889:16;17885:1;17877:6;17873:14;17866:40;17749:164;:::o;17919:366::-;18061:3;18082:67;18146:2;18141:3;18082:67;:::i;:::-;18075:74;;18158:93;18247:3;18158:93;:::i;:::-;18276:2;18271:3;18267:12;18260:19;;17919:366;;;:::o;18291:419::-;18457:4;18495:2;18484:9;18480:18;18472:26;;18544:9;18538:4;18534:20;18530:1;18519:9;18515:17;18508:47;18572:131;18698:4;18572:131;:::i;:::-;18564:139;;18291:419;;;:::o;18716:179::-;18856:31;18852:1;18844:6;18840:14;18833:55;18716:179;:::o;18901:366::-;19043:3;19064:67;19128:2;19123:3;19064:67;:::i;:::-;19057:74;;19140:93;19229:3;19140:93;:::i;:::-;19258:2;19253:3;19249:12;19242:19;;18901:366;;;:::o;19273:419::-;19439:4;19477:2;19466:9;19462:18;19454:26;;19526:9;19520:4;19516:20;19512:1;19501:9;19497:17;19490:47;19554:131;19680:4;19554:131;:::i;:::-;19546:139;;19273:419;;;:::o;19698:227::-;19838:34;19834:1;19826:6;19822:14;19815:58;19907:10;19902:2;19894:6;19890:15;19883:35;19698:227;:::o;19931:366::-;20073:3;20094:67;20158:2;20153:3;20094:67;:::i;:::-;20087:74;;20170:93;20259:3;20170:93;:::i;:::-;20288:2;20283:3;20279:12;20272:19;;19931:366;;;:::o;20303:419::-;20469:4;20507:2;20496:9;20492:18;20484:26;;20556:9;20550:4;20546:20;20542:1;20531:9;20527:17;20520:47;20584:131;20710:4;20584:131;:::i;:::-;20576:139;;20303:419;;;:::o;20728:221::-;20868:34;20864:1;20856:6;20852:14;20845:58;20937:4;20932:2;20924:6;20920:15;20913:29;20728:221;:::o;20955:366::-;21097:3;21118:67;21182:2;21177:3;21118:67;:::i;:::-;21111:74;;21194:93;21283:3;21194:93;:::i;:::-;21312:2;21307:3;21303:12;21296:19;;20955:366;;;:::o;21327:419::-;21493:4;21531:2;21520:9;21516:18;21508:26;;21580:9;21574:4;21570:20;21566:1;21555:9;21551:17;21544:47;21608:131;21734:4;21608:131;:::i;:::-;21600:139;;21327:419;;;:::o;21752:167::-;21892:19;21888:1;21880:6;21876:14;21869:43;21752:167;:::o;21925:366::-;22067:3;22088:67;22152:2;22147:3;22088:67;:::i;:::-;22081:74;;22164:93;22253:3;22164:93;:::i;:::-;22282:2;22277:3;22273:12;22266:19;;21925:366;;;:::o;22297:419::-;22463:4;22501:2;22490:9;22486:18;22478:26;;22550:9;22544:4;22540:20;22536:1;22525:9;22521:17;22514:47;22578:131;22704:4;22578:131;:::i;:::-;22570:139;;22297:419;;;:::o;22722:332::-;22843:4;22881:2;22870:9;22866:18;22858:26;;22894:71;22962:1;22951:9;22947:17;22938:6;22894:71;:::i;:::-;22975:72;23043:2;23032:9;23028:18;23019:6;22975:72;:::i;:::-;22722:332;;;;;:::o;23060:::-;23181:4;23219:2;23208:9;23204:18;23196:26;;23232:71;23300:1;23289:9;23285:17;23276:6;23232:71;:::i;:::-;23313:72;23381:2;23370:9;23366:18;23357:6;23313:72;:::i;:::-;23060:332;;;;;:::o;23398:223::-;23538:34;23534:1;23526:6;23522:14;23515:58;23607:6;23602:2;23594:6;23590:15;23583:31;23398:223;:::o;23627:366::-;23769:3;23790:67;23854:2;23849:3;23790:67;:::i;:::-;23783:74;;23866:93;23955:3;23866:93;:::i;:::-;23984:2;23979:3;23975:12;23968:19;;23627:366;;;:::o;23999:419::-;24165:4;24203:2;24192:9;24188:18;24180:26;;24252:9;24246:4;24242:20;24238:1;24227:9;24223:17;24216:47;24280:131;24406:4;24280:131;:::i;:::-;24272:139;;23999:419;;;:::o;24424:194::-;24464:4;24484:20;24502:1;24484:20;:::i;:::-;24479:25;;24518:20;24536:1;24518:20;:::i;:::-;24513:25;;24562:1;24559;24555:9;24547:17;;24586:1;24580:4;24577:11;24574:37;;;24591:18;;:::i;:::-;24574:37;24424:194;;;;:::o;24624:180::-;24672:77;24669:1;24662:88;24769:4;24766:1;24759:15;24793:4;24790:1;24783:15;24810:233;24849:3;24872:24;24890:5;24872:24;:::i;:::-;24863:33;;24918:66;24911:5;24908:77;24905:103;;24988:18;;:::i;:::-;24905:103;25035:1;25028:5;25024:13;25017:20;;24810:233;;;:::o;25049:191::-;25087:4;25107:18;25123:1;25107:18;:::i;:::-;25102:23;;25139:18;25155:1;25139:18;:::i;:::-;25134:23;;25181:1;25178;25174:9;25166:17;;25205:4;25199;25196:14;25193:40;;;25213:18;;:::i;:::-;25193:40;25049:191;;;;:::o;25246:180::-;25294:77;25291:1;25284:88;25391:4;25388:1;25381:15;25415:4;25412:1;25405:15;25432:176;25464:1;25481:20;25499:1;25481:20;:::i;:::-;25476:25;;25515:20;25533:1;25515:20;:::i;:::-;25510:25;;25554:1;25544:35;;25559:18;;:::i;:::-;25544:35;25600:1;25597;25593:9;25588:14;;25432:176;;;;:::o;25614:175::-;25754:27;25750:1;25742:6;25738:14;25731:51;25614:175;:::o;25795:366::-;25937:3;25958:67;26022:2;26017:3;25958:67;:::i;:::-;25951:74;;26034:93;26123:3;26034:93;:::i;:::-;26152:2;26147:3;26143:12;26136:19;;25795:366;;;:::o;26167:419::-;26333:4;26371:2;26360:9;26356:18;26348:26;;26420:9;26414:4;26410:20;26406:1;26395:9;26391:17;26384:47;26448:131;26574:4;26448:131;:::i;:::-;26440:139;;26167:419;;;:::o;26592:170::-;26732:22;26728:1;26720:6;26716:14;26709:46;26592:170;:::o;26768:366::-;26910:3;26931:67;26995:2;26990:3;26931:67;:::i;:::-;26924:74;;27007:93;27096:3;27007:93;:::i;:::-;27125:2;27120:3;27116:12;27109:19;;26768:366;;;:::o;27140:419::-;27306:4;27344:2;27333:9;27329:18;27321:26;;27393:9;27387:4;27383:20;27379:1;27368:9;27364:17;27357:47;27421:131;27547:4;27421:131;:::i;:::-;27413:139;;27140:419;;;:::o;27565:171::-;27705:23;27701:1;27693:6;27689:14;27682:47;27565:171;:::o;27742:366::-;27884:3;27905:67;27969:2;27964:3;27905:67;:::i;:::-;27898:74;;27981:93;28070:3;27981:93;:::i;:::-;28099:2;28094:3;28090:12;28083:19;;27742:366;;;:::o;28114:419::-;28280:4;28318:2;28307:9;28303:18;28295:26;;28367:9;28361:4;28357:20;28353:1;28342:9;28338:17;28331:47;28395:131;28521:4;28395:131;:::i;:::-;28387:139;;28114:419;;;:::o;28539:173::-;28679:25;28675:1;28667:6;28663:14;28656:49;28539:173;:::o;28718:366::-;28860:3;28881:67;28945:2;28940:3;28881:67;:::i;:::-;28874:74;;28957:93;29046:3;28957:93;:::i;:::-;29075:2;29070:3;29066:12;29059:19;;28718:366;;;:::o;29090:419::-;29256:4;29294:2;29283:9;29279:18;29271:26;;29343:9;29337:4;29333:20;29329:1;29318:9;29314:17;29307:47;29371:131;29497:4;29371:131;:::i;:::-;29363:139;;29090:419;;;:::o;29515:176::-;29655:28;29651:1;29643:6;29639:14;29632:52;29515:176;:::o;29697:366::-;29839:3;29860:67;29924:2;29919:3;29860:67;:::i;:::-;29853:74;;29936:93;30025:3;29936:93;:::i;:::-;30054:2;30049:3;30045:12;30038:19;;29697:366;;;:::o;30069:419::-;30235:4;30273:2;30262:9;30258:18;30250:26;;30322:9;30316:4;30312:20;30308:1;30297:9;30293:17;30286:47;30350:131;30476:4;30350:131;:::i;:::-;30342:139;;30069:419;;;:::o;30494:410::-;30534:7;30557:20;30575:1;30557:20;:::i;:::-;30552:25;;30591:20;30609:1;30591:20;:::i;:::-;30586:25;;30646:1;30643;30639:9;30668:30;30686:11;30668:30;:::i;:::-;30657:41;;30847:1;30838:7;30834:15;30831:1;30828:22;30808:1;30801:9;30781:83;30758:139;;30877:18;;:::i;:::-;30758:139;30542:362;30494:410;;;;:::o;30910:185::-;30950:1;30967:20;30985:1;30967:20;:::i;:::-;30962:25;;31001:20;31019:1;31001:20;:::i;:::-;30996:25;;31040:1;31030:35;;31045:18;;:::i;:::-;31030:35;31087:1;31084;31080:9;31075:14;;30910:185;;;;:::o;31101:177::-;31241:29;31237:1;31229:6;31225:14;31218:53;31101:177;:::o;31284:366::-;31426:3;31447:67;31511:2;31506:3;31447:67;:::i;:::-;31440:74;;31523:93;31612:3;31523:93;:::i;:::-;31641:2;31636:3;31632:12;31625:19;;31284:366;;;:::o;31656:419::-;31822:4;31860:2;31849:9;31845:18;31837:26;;31909:9;31903:4;31899:20;31895:1;31884:9;31880:17;31873:47;31937:131;32063:4;31937:131;:::i;:::-;31929:139;;31656:419;;;:::o;32081:143::-;32138:5;32169:6;32163:13;32154:22;;32185:33;32212:5;32185:33;:::i;:::-;32081:143;;;;:::o;32230:351::-;32300:6;32349:2;32337:9;32328:7;32324:23;32320:32;32317:119;;;32355:79;;:::i;:::-;32317:119;32475:1;32500:64;32556:7;32547:6;32536:9;32532:22;32500:64;:::i;:::-;32490:74;;32446:128;32230:351;;;;:::o;32587:77::-;32624:7;32653:5;32642:16;;32587:77;;;:::o;32670:118::-;32757:24;32775:5;32757:24;:::i;:::-;32752:3;32745:37;32670:118;;:::o;32794:101::-;32830:7;32870:18;32863:5;32859:30;32848:41;;32794:101;;;:::o;32901:115::-;32986:23;33003:5;32986:23;:::i;:::-;32981:3;32974:36;32901:115;;:::o;33022:89::-;33058:7;33098:6;33091:5;33087:18;33076:29;;33022:89;;;:::o;33117:115::-;33202:23;33219:5;33202:23;:::i;:::-;33197:3;33190:36;33117:115;;:::o;33238:::-;33323:23;33340:5;33323:23;:::i;:::-;33318:3;33311:36;33238:115;;:::o;33359:136::-;33406:9;33439:50;33456:32;33465:22;33481:5;33465:22;:::i;:::-;33456:32;:::i;:::-;33439:50;:::i;:::-;33426:63;;33359:136;;;:::o;33501:125::-;33585:34;33613:5;33585:34;:::i;:::-;33580:3;33573:47;33501:125;;:::o;33632:646::-;33828:4;33866:3;33855:9;33851:19;33843:27;;33880:71;33948:1;33937:9;33933:17;33924:6;33880:71;:::i;:::-;33961:70;34027:2;34016:9;34012:18;34003:6;33961:70;:::i;:::-;34041;34107:2;34096:9;34092:18;34083:6;34041:70;:::i;:::-;34121;34187:2;34176:9;34172:18;34163:6;34121:70;:::i;:::-;34201;34266:3;34255:9;34251:19;34242:6;34201:70;:::i;:::-;33632:646;;;;;;;;:::o;34284:434::-;34429:4;34467:2;34456:9;34452:18;34444:26;;34480:71;34548:1;34537:9;34533:17;34524:6;34480:71;:::i;:::-;34561:68;34625:2;34614:9;34610:18;34601:6;34561:68;:::i;:::-;34639:72;34707:2;34696:9;34692:18;34683:6;34639:72;:::i;:::-;34284:434;;;;;;:::o;34724:167::-;34864:19;34860:1;34852:6;34848:14;34841:43;34724:167;:::o;34897:366::-;35039:3;35060:67;35124:2;35119:3;35060:67;:::i;:::-;35053:74;;35136:93;35225:3;35136:93;:::i;:::-;35254:2;35249:3;35245:12;35238:19;;34897:366;;;:::o;35269:419::-;35435:4;35473:2;35462:9;35458:18;35450:26;;35522:9;35516:4;35512:20;35508:1;35497:9;35493:17;35486:47;35550:131;35676:4;35550:131;:::i;:::-;35542:139;;35269:419;;;:::o;35694:175::-;35834:27;35830:1;35822:6;35818:14;35811:51;35694:175;:::o;35875:366::-;36017:3;36038:67;36102:2;36097:3;36038:67;:::i;:::-;36031:74;;36114:93;36203:3;36114:93;:::i;:::-;36232:2;36227:3;36223:12;36216:19;;35875:366;;;:::o;36247:419::-;36413:4;36451:2;36440:9;36436:18;36428:26;;36500:9;36494:4;36490:20;36486:1;36475:9;36471:17;36464:47;36528:131;36654:4;36528:131;:::i;:::-;36520:139;;36247:419;;;:::o;36672:177::-;36812:29;36808:1;36800:6;36796:14;36789:53;36672:177;:::o;36855:366::-;36997:3;37018:67;37082:2;37077:3;37018:67;:::i;:::-;37011:74;;37094:93;37183:3;37094:93;:::i;:::-;37212:2;37207:3;37203:12;37196:19;;36855:366;;;:::o;37227:419::-;37393:4;37431:2;37420:9;37416:18;37408:26;;37480:9;37474:4;37470:20;37466:1;37455:9;37451:17;37444:47;37508:131;37634:4;37508:131;:::i;:::-;37500:139;;37227:419;;;:::o;37652:169::-;37792:21;37788:1;37780:6;37776:14;37769:45;37652:169;:::o;37827:366::-;37969:3;37990:67;38054:2;38049:3;37990:67;:::i;:::-;37983:74;;38066:93;38155:3;38066:93;:::i;:::-;38184:2;38179:3;38175:12;38168:19;;37827:366;;;:::o;38199:419::-;38365:4;38403:2;38392:9;38388:18;38380:26;;38452:9;38446:4;38442:20;38438:1;38427:9;38423:17;38416:47;38480:131;38606:4;38480:131;:::i;:::-;38472:139;;38199:419;;;:::o;38624:230::-;38764:34;38760:1;38752:6;38748:14;38741:58;38833:13;38828:2;38820:6;38816:15;38809:38;38624:230;:::o;38860:366::-;39002:3;39023:67;39087:2;39082:3;39023:67;:::i;:::-;39016:74;;39099:93;39188:3;39099:93;:::i;:::-;39217:2;39212:3;39208:12;39201:19;;38860:366;;;:::o;39232:419::-;39398:4;39436:2;39425:9;39421:18;39413:26;;39485:9;39479:4;39475:20;39471:1;39460:9;39456:17;39449:47;39513:131;39639:4;39513:131;:::i;:::-;39505:139;;39232:419;;;:::o;39657:188::-;39695:3;39714:18;39730:1;39714:18;:::i;:::-;39709:23;;39746:18;39762:1;39746:18;:::i;:::-;39741:23;;39787:1;39784;39780:9;39773:16;;39810:4;39805:3;39802:13;39799:39;;;39818:18;;:::i;:::-;39799:39;39657:188;;;;:::o;39851:170::-;39881:1;39898:18;39914:1;39898:18;:::i;:::-;39893:23;;39930:18;39946:1;39930:18;:::i;:::-;39925:23;;39967:1;39957:35;;39972:18;;:::i;:::-;39957:35;40013:1;40010;40006:9;40001:14;;39851:170;;;;:::o
Swarm Source
ipfs://2f17750668dcdefc304ba4a04de6d2e7ac58b20582b56c65fc4d5d4fcb4c7f5a
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.