Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- ErinaceusVRF
- Optimization enabled
- true
- Compiler version
- v0.8.6+commit.11564f7e
- Optimization runs
- 200
- EVM Version
- default
- Verified at
- 2025-08-20T01:35:27.958500Z
Constructor Arguments
000000000000000000000000fa559acc79e175b43d5c7e4d0371ff449d8bf5b7000000000000000000000000bb78efaaaf9223b4840ea7defdc379a13b16399b
Arg [0] (<b>address</b>) : <a href="{#{address_path(@conn, :show, @address)}}">0xfa559acc79e175b43d5c7e4d0371ff449d8bf5b7</a> Arg [1] (<b>address</b>) : <a href="{#{address_path(@conn, :show, @address)}}">0xbb78efaaaf9223b4840ea7defdc379a13b16399b</a>
contracts/vrf/ErinaceusVRF.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import {ErinaceusVRFInterface} from "./interfaces/ErinaceusVRFInterface.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {VRFConsumerBaseV2} from "./VRFConsumerBaseV2.sol";
import {IHashHub} from "../interfaces/IHashHub.sol";
import {VRF} from "./VRF.sol";
contract ErinaceusVRF is VRF, Ownable, ErinaceusVRFInterface {
IHashHub public HashHub;
address public team;
uint256 public withdrawableForTeam;
uint256 public feePercentage;
// 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 InvalidCalldata();
error CantBeAddressZero();
error TooManyConsumers();
error InsufficientBalance();
error InvalidSubscription();
error OnlyCallableFromLink();
error PendingRequestExists();
error PercentageIsNotInRange();
error MustBeSubOwner(address owner);
error BlockhashNotInStore(uint256 blocNumber);
event FundsRecovered(address to, uint256 amount);
error MustBeRequestedOwner(address proposedOwner);
error InvalidConsumer(uint64 subId, address consumer);
error BalanceInvariantViolated(uint256 internalBalance, uint256 externalBalance); // Should never happen
// We use the subscription struct (1 word)
// at fulfillment time.
struct Subscription {
uint256 balance; // Common FTN balance used for all consumer requests.
uint256 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 */ public s_subscriptionConfigs;
mapping(uint64 => Subscription) /* subId */ /* subscription */ public s_subscriptions;
mapping(uint256 => bool) private isRequested;
// 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 FTN sent to/from
// this contract through onTokenTransfer, cancelSubscription and oracleWithdraw.
// A discrepancy with this contract's FTN balance indicates someone
// sent tokens using transfer and so we may need to use recoverFunds.
uint256 private s_totalBalance;
uint256 public HashHubBalance;
uint256 public HashHubReward;
event RewardSet(uint256 reward);
event HashHubFunded(uint256 oldBalance, uint256 newBalance);
event HashHubChanged(address oldHashHub, address newHashHub);
event SubscriptionCreated(uint64 indexed subId, address owner);
event SubscriptionConsumerAdded(uint64 indexed subId, address consumer);
event SubscriptionConsumerRemoved(uint64 indexed subId, address consumer);
event SubscriptionCanceled(uint64 indexed subId, address to, uint256 amount);
event SubscriptionOwnerTransferred(uint64 indexed subId, address from, address to);
event SubscriptionFunded(uint64 indexed subId, uint256 oldBalance, uint256 newBalance);
event SubscriptionOwnerTransferRequested(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 InsufficientGasForConsumer(uint256 have, uint256 want);
error NoCorrespondingRequest();
error IncorrectCommitment();
error InvalidBlockhash(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 => uint256) /* oracle */ /* FTN 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, uint256 payment, bool success);
struct Config {
uint16 minimumRequestConfirmations;
uint32 maxGasLimit;
// Reentrancy protection.
bool reentrancyLock;
// Gas to cover oracle payment after we calculate the payment.
// We make it configurable in case those operations are repriced.
uint32 gasAfterPaymentCalculation;
}
Config private s_config;
FeeConfig private s_feeConfig;
struct FeeConfig {
// Flat fee charged per fulfillment in millionths of FTN
// So fee range is [0, 2^32/10^6].
uint32 fulfillmentFlatFeeFTNPPMTier1;
uint32 fulfillmentFlatFeeFTNPPMTier2;
uint32 fulfillmentFlatFeeFTNPPMTier3;
uint32 fulfillmentFlatFeeFTNPPMTier4;
uint32 fulfillmentFlatFeeFTNPPMTier5;
uint24 reqsForTier2;
uint24 reqsForTier3;
uint24 reqsForTier4;
uint24 reqsForTier5;
}
event ConfigSet(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 gasAfterPaymentCalculation,
FeeConfig feeConfig
);
constructor(
address _hashHub,
address _owner
){
HashHub = IHashHub(_hashHub);
_transferOwnership(_owner);
}
/**
* @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 erinaceus
* @param minimumRequestConfirmations global min for request confirmations
* @param maxGasLimit global max for request gas limit
* @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
* @param feeConfig fee tier configuration
*/
function setConfig(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 gasAfterPaymentCalculation,
FeeConfig memory feeConfig
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
minimumRequestConfirmations,
minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
s_config = Config({
minimumRequestConfirmations: minimumRequestConfirmations,
maxGasLimit: maxGasLimit,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
reentrancyLock: false
});
s_feeConfig = feeConfig;
emit ConfigSet(
minimumRequestConfirmations,
maxGasLimit,
gasAfterPaymentCalculation,
s_feeConfig
);
}
function setRewardForHashHub(
uint256 _amount
) external onlyOwner {
HashHubReward = _amount;
emit RewardSet(_amount);
}
function setHushHub(
address _hashHub
) external onlyOwner {
address oldHashHub = address(HashHub);
HashHub = IHashHub(_hashHub);
emit HashHubChanged(oldHashHub, address(HashHub));
}
function setTeam(address _teamAddress, uint256 _feePercentage) external onlyOwner {
if(_teamAddress == address(0)){
revert CantBeAddressZero();
}
if(_feePercentage > 100){
revert PercentageIsNotInRange();
}
team = _teamAddress;
feePercentage = _feePercentage;
}
function getConfig()
external
view
returns (
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 gasAfterPaymentCalculation
)
{
return (
s_config.minimumRequestConfirmations,
s_config.maxGasLimit,
s_config.gasAfterPaymentCalculation
);
}
function getFeeConfig()
external
view
returns (
uint32 fulfillmentFlatFeeFTNPPMTier1,
uint32 fulfillmentFlatFeeFTNPPMTier2,
uint32 fulfillmentFlatFeeFTNPPMTier3,
uint32 fulfillmentFlatFeeFTNPPMTier4,
uint32 fulfillmentFlatFeeFTNPPMTier5,
uint24 reqsForTier2,
uint24 reqsForTier3,
uint24 reqsForTier4,
uint24 reqsForTier5
)
{
return (
s_feeConfig.fulfillmentFlatFeeFTNPPMTier1,
s_feeConfig.fulfillmentFlatFeeFTNPPMTier2,
s_feeConfig.fulfillmentFlatFeeFTNPPMTier3,
s_feeConfig.fulfillmentFlatFeeFTNPPMTier4,
s_feeConfig.fulfillmentFlatFeeFTNPPMTier5,
s_feeConfig.reqsForTier2,
s_feeConfig.reqsForTier3,
s_feeConfig.reqsForTier4,
s_feeConfig.reqsForTier5
);
}
function getTotalBalance() external view returns (uint256) {
return s_totalBalance;
}
function getWithdrawableAmount() external view returns(uint256) {
return s_withdrawableTokens[msg.sender];
}
/**
* @notice Owner cancel subscription, sends remaining link 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 link sent with transfer instead of transferAndCall.
* @param to address to send link to
*/
function recoverFunds(address to) external onlyOwner {
uint256 externalBalance = (address(this)).balance - HashHubBalance;
uint256 internalBalance = uint256(s_totalBalance);
if (internalBalance > externalBalance) {
revert BalanceInvariantViolated(internalBalance, externalBalance);
}
if (internalBalance < externalBalance) {
uint256 amount = externalBalance - internalBalance;
// FTN.transfer(to, amount);
_sendViaCall(payable(to), amount);
emit FundsRecovered(to, amount);
}
// If the balances are equal, nothing to be done.
}
/**
* @inheritdoc ErinaceusVRFInterface
*/
function getRequestConfig() external view override returns (uint16, uint32, bytes32[] memory) {
return (s_config.minimumRequestConfirmations, s_config.maxGasLimit, s_provingKeyHashes);
}
/**
* @inheritdoc ErinaceusVRFInterface
*/
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)
);
if(!isRequested[block.number]){
if(HashHubBalance >= HashHubReward && HashHubBalance > 0){
HashHub.requestBlockhash{value: HashHubReward}(block.number);
HashHubBalance -= HashHubReward;
isRequested[block.number] = true;
}
}
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) {
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 = HashHub.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
return (keyHash, requestId, randomness);
}
/*
* @notice Compute fee based on the request count
* @param reqCount number of requests
* @return feePPM fee in FTN PPM
*/
function getFeeTier(uint256 reqCount) public view returns (uint32) {
FeeConfig memory fc = s_feeConfig;
if (0 <= reqCount && reqCount <= fc.reqsForTier2) {
return fc.fulfillmentFlatFeeFTNPPMTier1;
}
if (fc.reqsForTier2 < reqCount && reqCount <= fc.reqsForTier3) {
return fc.fulfillmentFlatFeeFTNPPMTier2;
}
if (fc.reqsForTier3 < reqCount && reqCount <= fc.reqsForTier4) {
return fc.fulfillmentFlatFeeFTNPPMTier3;
}
if (fc.reqsForTier4 < reqCount && reqCount <= fc.reqsForTier5) {
return fc.fulfillmentFlatFeeFTNPPMTier4;
}
return fc.fulfillmentFlatFeeFTNPPMTier5;
}
/*
* @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 (uint256) {
uint256 startGas = gasleft();
(bytes32 keyHash, uint256 requestId, uint256 randomness) = _getRandomnessFromProof(proof, rc); // TEST
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 erinaceus 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.
uint256 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 FTN fee to the payment amount.
// Its specified in millionths of FTN, if s_config.fulfillmentFlatFeeFTNPPM = 1
// 1 FTN / 1e6 = 1e18 juels / 1e6 = 1e12 juels.
uint256 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 * (100 - feePercentage) / 100;
withdrawableForTeam += payment * feePercentage / 100;
// 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 fulfillmentFlatFeeFTNPPM,
uint256 weiPerUnitGas
) internal view returns (uint256) {
//(juels/link) (wei/gas * gas) = juels
uint256 paymentNoFee = weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft());
uint256 fee = 1e12 * uint256(fulfillmentFlatFeeFTNPPM);
return uint96(paymentNoFee + fee);
}
/*
* @notice Oracle withdraw FTN 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 (!FTN.transfer(recipient, amount)) {
// revert InsufficientBalance();
// }
_sendViaCall(payable(recipient), amount);
}
function withdrawForTeam(uint256 amount) external nonReentrant{
if (withdrawableForTeam < amount) {
revert InsufficientBalance();
}
withdrawableForTeam -= amount;
s_totalBalance -= amount;
// if (!FTN.transfer(team, amount)) {
// revert InsufficientBalance();
// }
_sendViaCall(payable(team), amount);
}
function fundSubscribtion(uint64 subId) external payable nonReentrant {
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(msg.value);
s_totalBalance += uint96(msg.value);
// FTN.transferFrom(msg.sender, address(this), amount);
emit SubscriptionFunded(subId, oldBalance, oldBalance + msg.value);
}
function fundHashHub() external payable nonReentrant{
// FTN.transferFrom(msg.sender, address(this), amount);
uint256 oldBalance = HashHubBalance;
HashHubBalance += msg.value;
emit HashHubFunded(oldBalance, HashHubBalance);
}
function getCurrentSubId() external view returns (uint64) {
return s_currentSubId;
}
/**
* @inheritdoc ErinaceusVRFInterface
*/
function getSubscription(
uint64 subId
) external view override returns (uint256 balance, uint256 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 ErinaceusVRFInterface
*/
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 ErinaceusVRFInterface
*/
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 ErinaceusVRFInterface
*/
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 ErinaceusVRFInterface
*/
function removeConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
if (pendingRequestExists(subId)) {
revert PendingRequestExists();
}
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 ErinaceusVRFInterface
*/
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 ErinaceusVRFInterface
*/
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];
uint256 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 (!FTN.transfer(to, uint256(balance))) {
// revert InsufficientBalance();
// }
_sendViaCall(payable(to), uint256(balance));
emit SubscriptionCanceled(subId, to, balance);
}
/**
* @inheritdoc ErinaceusVRFInterface
* @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;
}
/**
* @notice Internal function to safely send FTN.
* @param to Recipient address.
* @param amount Amount of ETH to send.
*/
function _sendViaCall(
address payable to,
uint256 amount
) internal {
(bool sent, ) = to.call{value: amount} ("");
if (!sent) {
revert();
}
}
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();
}
_;
}
}
@openzeppelin/contracts/access/Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
@openzeppelin/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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;
}
}
contracts/interfaces/IHashHub.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
/**
* @title HashHub
* @notice This contract provides a way to access blockhashes older than
* the 256 block limit imposed by the BLOCKHASH opcode.
* You may assume that any blockhash stored by the contract is correct.
* Note that the contract depends on the format of serialized Ethereum
* blocks. If a future hardfork of Ethereum changes that format, the
* logic in this contract may become incorrect and an updated version
* would have to be deployed.
*/
interface IHashHub {
function store(uint256 n) external;
function requestBlockhash(uint256 _blockNumber) external payable;
function claimRewardsForUnregisteredBlocks(uint256[] memory _blocks) external;
function getBlockhash(uint256 n) external view returns (bytes32);
function rewardForStoring(uint256) external view returns(uint256);
function usersRequestedBlocks(address) external view returns(uint256[] memory);
function providedRewards(address, uint256) external view returns(uint256);
}
contracts/vrf/VRF.sol
// SPDX-License-Identifier: MIT
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 {
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) {
// solhint-disable-next-line custom-errors
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]
// solhint-disable-next-line custom-errors
require(p[0] < FIELD_SIZE, "invalid x-ordinate");
// solhint-disable-next-line custom-errors
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_)));
}
return 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];
}
}
return p;
}
// 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]));
}
return rv;
}
/** *********************************************************************
* @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) {
// solhint-disable-next-line custom-errors
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));
}
return (x3, z3);
}
// 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));
return (x3, z3);
}
/** **************************************************************************
@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;
}
}
return (sx, sy, sz);
}
// 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]);
// solhint-disable-next-line custom-errors
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 {
// solhint-disable-next-line custom-errors
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
// solhint-disable-next-line custom-errors
require((cp1Witness[0] % FIELD_SIZE) != (sp2Witness[0] % FIELD_SIZE), "points in sum must be distinct");
// solhint-disable-next-line custom-errors
require(_ecmulVerify(p1, c, cp1Witness), "First mul check failed");
// solhint-disable-next-line custom-errors
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 {
// solhint-disable-next-line custom-errors
require(_isOnCurve(pk), "public key is not on curve");
// solhint-disable-next-line custom-errors
require(_isOnCurve(gamma), "gamma is not on curve");
// solhint-disable-next-line custom-errors
require(_isOnCurve(cGammaWitness), "cGammaWitness is not on curve");
// solhint-disable-next-line custom-errors
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.)
// solhint-disable-next-line custom-errors
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);
// solhint-disable-next-line custom-errors
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)));
return output;
}
}
contracts/vrf/VRFConsumerBaseV2.sol
// SPDX-License-Identifier: MIT
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 ErinaceusVRF
* @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 _erinaceusVRF, address _link)
* @dev VRFConsumerBase(_erinaceusVRF) 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 ErinaceusVRFInterface
* @dev subscription management functions).
* @dev Call requestRandomWords(keyHash, subId, minimumRequestConfirmations,
* @dev callbackGasLimit, numWords),
* @dev see (ErinaceusVRFInterface for a description of the arguments).
*
* @dev Once the ErinaceusVRF 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 OnlyErinaceusCanFulfill(address have, address want);
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
address private immutable erinaceusVRF;
/**
* @param _erinaceusVRF address of ErinaceusVRF contract
*/
constructor(address _erinaceusVRF) {
erinaceusVRF = _erinaceusVRF;
}
/**
* @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
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal virtual;
// rawFulfillRandomness is called by ErinaceusVRF 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 != erinaceusVRF) {
revert OnlyErinaceusCanFulfill(msg.sender, erinaceusVRF);
}
fulfillRandomWords(requestId, randomWords);
}
}
contracts/vrf/interfaces/ErinaceusVRFInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ErinaceusVRFInterface {
/**
* @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 ErinaceusVRF 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 LINKTOKEN.transferAndCall(
* @dev address(ERINACEUS),
* @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 - LINK 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 (uint256 balance, uint256 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 LINK 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);
}
Compiler Settings
{"outputSelection":{"*":{"*":["*"],"":["*"]}},"optimizer":{"runs":200,"enabled":true},"metadata":{"useLiteralContent":true},"libraries":{}}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_hashHub","internalType":"address"},{"type":"address","name":"_owner","internalType":"address"}]},{"type":"error","name":"BalanceInvariantViolated","inputs":[{"type":"uint256","name":"internalBalance","internalType":"uint256"},{"type":"uint256","name":"externalBalance","internalType":"uint256"}]},{"type":"error","name":"BlockhashNotInStore","inputs":[{"type":"uint256","name":"blocNumber","internalType":"uint256"}]},{"type":"error","name":"CantBeAddressZero","inputs":[]},{"type":"error","name":"GasLimitTooBig","inputs":[{"type":"uint32","name":"have","internalType":"uint32"},{"type":"uint32","name":"want","internalType":"uint32"}]},{"type":"error","name":"IncorrectCommitment","inputs":[]},{"type":"error","name":"InsufficientBalance","inputs":[]},{"type":"error","name":"InsufficientGasForConsumer","inputs":[{"type":"uint256","name":"have","internalType":"uint256"},{"type":"uint256","name":"want","internalType":"uint256"}]},{"type":"error","name":"InvalidBlockhash","inputs":[{"type":"uint256","name":"blockNum","internalType":"uint256"}]},{"type":"error","name":"InvalidCalldata","inputs":[]},{"type":"error","name":"InvalidConsumer","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"address","name":"consumer","internalType":"address"}]},{"type":"error","name":"InvalidRequestConfirmations","inputs":[{"type":"uint16","name":"have","internalType":"uint16"},{"type":"uint16","name":"min","internalType":"uint16"},{"type":"uint16","name":"max","internalType":"uint16"}]},{"type":"error","name":"InvalidSubscription","inputs":[]},{"type":"error","name":"MustBeRequestedOwner","inputs":[{"type":"address","name":"proposedOwner","internalType":"address"}]},{"type":"error","name":"MustBeSubOwner","inputs":[{"type":"address","name":"owner","internalType":"address"}]},{"type":"error","name":"NoCorrespondingRequest","inputs":[]},{"type":"error","name":"NoSuchProvingKey","inputs":[{"type":"bytes32","name":"keyHash","internalType":"bytes32"}]},{"type":"error","name":"NumWordsTooBig","inputs":[{"type":"uint32","name":"have","internalType":"uint32"},{"type":"uint32","name":"want","internalType":"uint32"}]},{"type":"error","name":"OnlyCallableFromLink","inputs":[]},{"type":"error","name":"PaymentTooLarge","inputs":[]},{"type":"error","name":"PendingRequestExists","inputs":[]},{"type":"error","name":"PercentageIsNotInRange","inputs":[]},{"type":"error","name":"ProvingKeyAlreadyRegistered","inputs":[{"type":"bytes32","name":"keyHash","internalType":"bytes32"}]},{"type":"error","name":"Reentrant","inputs":[]},{"type":"error","name":"TooManyConsumers","inputs":[]},{"type":"event","name":"ConfigSet","inputs":[{"type":"uint16","name":"minimumRequestConfirmations","internalType":"uint16","indexed":false},{"type":"uint32","name":"maxGasLimit","internalType":"uint32","indexed":false},{"type":"uint32","name":"gasAfterPaymentCalculation","internalType":"uint32","indexed":false},{"type":"tuple","name":"feeConfig","internalType":"struct ErinaceusVRF.FeeConfig","indexed":false,"components":[{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier1","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier2","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier3","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier4","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier5","internalType":"uint32"},{"type":"uint24","name":"reqsForTier2","internalType":"uint24"},{"type":"uint24","name":"reqsForTier3","internalType":"uint24"},{"type":"uint24","name":"reqsForTier4","internalType":"uint24"},{"type":"uint24","name":"reqsForTier5","internalType":"uint24"}]}],"anonymous":false},{"type":"event","name":"FundsRecovered","inputs":[{"type":"address","name":"to","internalType":"address","indexed":false},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"HashHubChanged","inputs":[{"type":"address","name":"oldHashHub","internalType":"address","indexed":false},{"type":"address","name":"newHashHub","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"HashHubFunded","inputs":[{"type":"uint256","name":"oldBalance","internalType":"uint256","indexed":false},{"type":"uint256","name":"newBalance","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"ProvingKeyDeregistered","inputs":[{"type":"bytes32","name":"keyHash","internalType":"bytes32","indexed":false},{"type":"address","name":"oracle","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"ProvingKeyRegistered","inputs":[{"type":"bytes32","name":"keyHash","internalType":"bytes32","indexed":false},{"type":"address","name":"oracle","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"RandomWordsFulfilled","inputs":[{"type":"uint256","name":"requestId","internalType":"uint256","indexed":true},{"type":"uint256","name":"outputSeed","internalType":"uint256","indexed":false},{"type":"uint256","name":"payment","internalType":"uint256","indexed":false},{"type":"bool","name":"success","internalType":"bool","indexed":false}],"anonymous":false},{"type":"event","name":"RandomWordsRequested","inputs":[{"type":"bytes32","name":"keyHash","internalType":"bytes32","indexed":true},{"type":"uint256","name":"requestId","internalType":"uint256","indexed":false},{"type":"uint256","name":"preSeed","internalType":"uint256","indexed":false},{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"uint16","name":"minimumRequestConfirmations","internalType":"uint16","indexed":false},{"type":"uint32","name":"callbackGasLimit","internalType":"uint32","indexed":false},{"type":"uint32","name":"numWords","internalType":"uint32","indexed":false},{"type":"address","name":"sender","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"RewardSet","inputs":[{"type":"uint256","name":"reward","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionCanceled","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":false},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionConsumerAdded","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"address","name":"consumer","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionConsumerRemoved","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"address","name":"consumer","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionCreated","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"address","name":"owner","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionFunded","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"uint256","name":"oldBalance","internalType":"uint256","indexed":false},{"type":"uint256","name":"newBalance","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionOwnerTransferRequested","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"address","name":"from","internalType":"address","indexed":false},{"type":"address","name":"to","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"SubscriptionOwnerTransferred","inputs":[{"type":"uint64","name":"subId","internalType":"uint64","indexed":true},{"type":"address","name":"from","internalType":"address","indexed":false},{"type":"address","name":"to","internalType":"address","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IHashHub"}],"name":"HashHub","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"HashHubBalance","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"HashHubReward","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"}],"name":"MAX_CONSUMERS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint32","name":"","internalType":"uint32"}],"name":"MAX_NUM_WORDS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"}],"name":"MAX_REQUEST_CONFIRMATIONS","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"acceptSubscriptionOwnerTransfer","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addConsumer","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"address","name":"consumer","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"cancelSubscription","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint64","name":"","internalType":"uint64"}],"name":"createSubscription","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"deregisterProvingKey","inputs":[{"type":"uint256[2]","name":"publicProvingKey","internalType":"uint256[2]"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"feePercentage","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"fulfillRandomWords","inputs":[{"type":"tuple","name":"proof","internalType":"struct VRF.Proof","components":[{"type":"uint256[2]","name":"pk","internalType":"uint256[2]"},{"type":"uint256[2]","name":"gamma","internalType":"uint256[2]"},{"type":"uint256","name":"c","internalType":"uint256"},{"type":"uint256","name":"s","internalType":"uint256"},{"type":"uint256","name":"seed","internalType":"uint256"},{"type":"address","name":"uWitness","internalType":"address"},{"type":"uint256[2]","name":"cGammaWitness","internalType":"uint256[2]"},{"type":"uint256[2]","name":"sHashWitness","internalType":"uint256[2]"},{"type":"uint256","name":"zInv","internalType":"uint256"}]},{"type":"tuple","name":"rc","internalType":"struct ErinaceusVRF.RequestCommitment","components":[{"type":"uint64","name":"blockNum","internalType":"uint64"},{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"uint32","name":"callbackGasLimit","internalType":"uint32"},{"type":"uint32","name":"numWords","internalType":"uint32"},{"type":"address","name":"sender","internalType":"address"}]}]},{"type":"function","stateMutability":"payable","outputs":[],"name":"fundHashHub","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"fundSubscribtion","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"getCommitment","inputs":[{"type":"uint256","name":"requestId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"minimumRequestConfirmations","internalType":"uint16"},{"type":"uint32","name":"maxGasLimit","internalType":"uint32"},{"type":"uint32","name":"gasAfterPaymentCalculation","internalType":"uint32"}],"name":"getConfig","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint64","name":"","internalType":"uint64"}],"name":"getCurrentSubId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier1","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier2","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier3","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier4","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier5","internalType":"uint32"},{"type":"uint24","name":"reqsForTier2","internalType":"uint24"},{"type":"uint24","name":"reqsForTier3","internalType":"uint24"},{"type":"uint24","name":"reqsForTier4","internalType":"uint24"},{"type":"uint24","name":"reqsForTier5","internalType":"uint24"}],"name":"getFeeConfig","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint32","name":"","internalType":"uint32"}],"name":"getFeeTier","inputs":[{"type":"uint256","name":"reqCount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint16","name":"","internalType":"uint16"},{"type":"uint32","name":"","internalType":"uint32"},{"type":"bytes32[]","name":"","internalType":"bytes32[]"}],"name":"getRequestConfig","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"balance","internalType":"uint256"},{"type":"uint256","name":"reqCount","internalType":"uint256"},{"type":"address","name":"owner","internalType":"address"},{"type":"address[]","name":"consumers","internalType":"address[]"}],"name":"getSubscription","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getTotalBalance","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getWithdrawableAmount","inputs":[]},{"type":"function","stateMutability":"pure","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"hashOfKey","inputs":[{"type":"uint256[2]","name":"publicKey","internalType":"uint256[2]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"oracleWithdraw","inputs":[{"type":"address","name":"recipient","internalType":"address"},{"type":"uint96","name":"amount","internalType":"uint96"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"ownerCancelSubscription","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"pendingRequestExists","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"recoverFunds","inputs":[{"type":"address","name":"to","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"registerProvingKey","inputs":[{"type":"address","name":"oracle","internalType":"address"},{"type":"uint256[2]","name":"publicProvingKey","internalType":"uint256[2]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeConsumer","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"address","name":"consumer","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"requestRandomWords","inputs":[{"type":"bytes32","name":"keyHash","internalType":"bytes32"},{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"uint16","name":"requestConfirmations","internalType":"uint16"},{"type":"uint32","name":"callbackGasLimit","internalType":"uint32"},{"type":"uint32","name":"numWords","internalType":"uint32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"requestSubscriptionOwnerTransfer","inputs":[{"type":"uint64","name":"subId","internalType":"uint64"},{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"requestedOwner","internalType":"address"}],"name":"s_subscriptionConfigs","inputs":[{"type":"uint64","name":"","internalType":"uint64"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"balance","internalType":"uint256"},{"type":"uint256","name":"reqCount","internalType":"uint256"}],"name":"s_subscriptions","inputs":[{"type":"uint64","name":"","internalType":"uint64"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setConfig","inputs":[{"type":"uint16","name":"minimumRequestConfirmations","internalType":"uint16"},{"type":"uint32","name":"maxGasLimit","internalType":"uint32"},{"type":"uint32","name":"gasAfterPaymentCalculation","internalType":"uint32"},{"type":"tuple","name":"feeConfig","internalType":"struct ErinaceusVRF.FeeConfig","components":[{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier1","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier2","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier3","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier4","internalType":"uint32"},{"type":"uint32","name":"fulfillmentFlatFeeFTNPPMTier5","internalType":"uint32"},{"type":"uint24","name":"reqsForTier2","internalType":"uint24"},{"type":"uint24","name":"reqsForTier3","internalType":"uint24"},{"type":"uint24","name":"reqsForTier4","internalType":"uint24"},{"type":"uint24","name":"reqsForTier5","internalType":"uint24"}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHushHub","inputs":[{"type":"address","name":"_hashHub","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setRewardForHashHub","inputs":[{"type":"uint256","name":"_amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setTeam","inputs":[{"type":"address","name":"_teamAddress","internalType":"address"},{"type":"uint256","name":"_feePercentage","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"team","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"withdrawForTeam","inputs":[{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"withdrawableForTeam","inputs":[]}]
Contract Creation Code
0x60806040523480156200001157600080fd5b506040516200468d3803806200468d8339810160408190526200003491620000da565b6200003f336200006d565b600180546001600160a01b0319166001600160a01b03841617905562000065816200006d565b505062000112565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80516001600160a01b0381168114620000d557600080fd5b919050565b60008060408385031215620000ee57600080fd5b620000f983620000bd565b91506200010960208401620000bd565b90509250929050565b61456b80620001226000396000f3fe6080604052600436106102715760003560e01c806385f2aef21161014f578063bc14edcf116100c1578063e4e1b45d1161007a578063e4e1b45d14610882578063e72f6e30146108a2578063e82ad7d4146108c2578063eccea3e2146108f2578063ee4201fe14610912578063f2fde38b1461092557600080fd5b8063bc14edcf14610748578063c3f909d414610768578063c8f7facc146107c1578063caf70c4a146107e1578063d7ae1d3014610801578063d7ca6f9b1461082157600080fd5b8063a0a19abd11610113578063a0a19abd146106a5578063a21a23e4146106c5578063a47c7696146106da578063af198b971461070a578063aff4fb231461072a578063b5a697a81461074057600080fd5b806385f2aef2146105f75780638da5cb5b1461062f57806390be10cc1461064d5780639f87fad71461066f578063a001ecdd1461068f57600080fd5b8063572727b0116101e85780636840c05e116101ac5780636840c05e1461050c57806369bcdb7d146105555780636f64f03f14610582578063715018a6146105a25780637341c10c146105b757806382359740146105d757600080fd5b8063572727b0146103f55780635d3b1d30146104155780635fbbc0d21461043557806364d51a2a146104d757806366316d8d146104ec57600080fd5b806312b583491161023a57806312b583491461033757806315c48b8414610356578063167957bd1461037e5780631d16919b1461039457806340d6bb82146103b45780634c6ad91c146103df57600080fd5b80620122911461027657806302bcc5b6146102a357806304c357cb146102c557806306bfa637146102e557806308821d5814610317575b600080fd5b34801561028257600080fd5b5061028b610945565b60405161029a939291906141c5565b60405180910390f35b3480156102af57600080fd5b506102c36102be3660046140bd565b6109c1565b005b3480156102d157600080fd5b506102c36102e03660046140d8565b610a38565b3480156102f157600080fd5b506009546001600160401b03165b6040516001600160401b03909116815260200161029a565b34801561032357600080fd5b506102c3610332366004613e2c565b610b83565b34801561034357600080fd5b50600a545b60405190815260200161029a565b34801561036257600080fd5b5061036b60c881565b60405161ffff909116815260200161029a565b34801561038a57600080fd5b5061034860035481565b3480156103a057600080fd5b506102c36103af366004613fa3565b610d17565b3480156103c057600080fd5b506103ca6101f481565b60405163ffffffff909116815260200161029a565b3480156103eb57600080fd5b50610348600b5481565b34801561040157600080fd5b506102c36104103660046140a4565b610f17565b34801561042157600080fd5b50610348610430366004613e7d565b610fab565b34801561044157600080fd5b506012546040805163ffffffff8084168252640100000000840481166020830152600160401b8404811692820192909252600160601b830482166060820152600160801b8304909116608082015262ffffff600160a01b8304811660a0830152600160b81b8304811660c0830152600160d01b8304811660e0830152600160e81b9092049091166101008201526101200161029a565b3480156104e357600080fd5b5061036b606481565b3480156104f857600080fd5b506102c3610507366004613de9565b611342565b34801561051857600080fd5b506105406105273660046140bd565b6007602052600090815260409020805460019091015482565b6040805192835260208301919091520161029a565b34801561056157600080fd5b506103486105703660046140a4565b60009081526010602052604090205490565b34801561058e57600080fd5b506102c361059d366004613d8b565b61140d565b3480156105ae57600080fd5b506102c361150c565b3480156105c357600080fd5b506102c36105d23660046140d8565b611520565b3480156105e357600080fd5b506102c36105f23660046140bd565b6116d2565b34801561060357600080fd5b50600254610617906001600160a01b031681565b6040516001600160a01b03909116815260200161029a565b34801561063b57600080fd5b506000546001600160a01b0316610617565b34801561065957600080fd5b50336000908152600f6020526040902054610348565b34801561067b57600080fd5b506102c361068a3660046140d8565b61182f565b34801561069b57600080fd5b5061034860045481565b3480156106b157600080fd5b50600154610617906001600160a01b031681565b3480156106d157600080fd5b506102ff611b80565b3480156106e657600080fd5b506106fa6106f53660046140bd565b611d07565b60405161029a9493929190614351565b34801561071657600080fd5b50610348610725366004613edb565b611df5565b34801561073657600080fd5b50610348600c5481565b6102c361216f565b34801561075457600080fd5b506102c3610763366004613dbf565b6121f3565b34801561077457600080fd5b5061079a60115461ffff81169163ffffffff620100008304811692600160381b90041690565b6040805161ffff909416845263ffffffff928316602085015291169082015260600161029a565b3480156107cd57600080fd5b506102c36107dc3660046140a4565b61226a565b3480156107ed57600080fd5b506103486107fc366004613e48565b6122a7565b34801561080d57600080fd5b506102c361081c3660046140d8565b6122d7565b34801561082d57600080fd5b5061086261083c3660046140bd565b600660205260009081526040902080546001909101546001600160a01b03918216911682565b604080516001600160a01b0393841681529290911660208301520161029a565b34801561088e57600080fd5b506102c361089d366004613d70565b6123a9565b3480156108ae57600080fd5b506102c36108bd366004613d70565b612412565b3480156108ce57600080fd5b506108e26108dd3660046140bd565b6124c0565b604051901515815260200161029a565b3480156108fe57600080fd5b506103ca61090d3660046140a4565b61266b565b6102c36109203660046140bd565b6127a9565b34801561093157600080fd5b506102c3610940366004613d70565b6128b6565b601154600e805460408051602080840282018101909252828152600094859460609461ffff8316946201000090930463ffffffff169391928391908301828280156109af57602002820191906000526020600020905b81548152602001906001019080831161099b575b50505050509050925092509250909192565b6109c961292c565b6001600160401b0381166000908152600660205260409020546001600160a01b0316610a0857604051630fb532db60e11b815260040160405180910390fd5b6001600160401b038116600090815260066020526040902054610a359082906001600160a01b0316612986565b50565b6001600160401b03821660009081526006602052604090205482906001600160a01b031680610a7a57604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b03821614610ab357604051636c51fda960e11b81526001600160a01b03821660048201526024015b60405180910390fd5b601154600160301b900460ff1615610ade5760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0384166000908152600660205260409020600101546001600160a01b03848116911614610b7d576001600160401b03841660008181526006602090815260409182902060010180546001600160a01b0319166001600160a01b0388169081179091558251338152918201527f69436ea6df009049404f564eff6622cd00522b0bd6a89efd9e52a355c4a879be91015b60405180910390a25b50505050565b610b8b61292c565b604080518082018252600091610bba9190849060029083908390808284376000920191909152506122a7915050565b6000818152600d60205260409020549091506001600160a01b031680610bf657604051631dfd6e1360e21b815260048101839052602401610aaa565b6000828152600d6020526040812080546001600160a01b03191690555b600e54811015610cce5782600e8281548110610c3157610c31614509565b90600052602060002001541415610cbc57600e805460009190610c569060019061445a565b81548110610c6657610c66614509565b9060005260206000200154905080600e8381548110610c8757610c87614509565b600091825260209091200155600e805480610ca457610ca46144f3565b60019003818190600052602060002001600090559055505b80610cc681614471565b915050610c13565b50806001600160a01b03167f72be339577868f868798bac2c93e52d6f034fef4689a9848996c14ebb7416c0d83604051610d0a91815260200190565b60405180910390a2505050565b610d1f61292c565b60c861ffff85161115610d595760405163539c34bb60e11b815261ffff851660048201819052602482015260c86044820152606401610aaa565b604080516080808201835261ffff871680835263ffffffff878116602080860182905260008688015288831660609687018190526011805465ffffffffffff191690951762010000909302929092176affffffffff0000000000001916600160381b909202919091179092558551601280549388015188880151968901519589015160a08a015160c08b015160e08c01516101008d015196881667ffffffffffffffff199099169890981764010000000094881694909402939093176fffffffffffffffff00000000000000001916600160401b9987169990990263ffffffff60601b191698909817600160601b978616979097029690961766ffffffffffffff60801b1916600160801b969094169590950262ffffff60a01b191692909217600160a01b62ffffff968716021765ffffffffffff60b81b1916600160b81b9486169490940262ffffff60d01b191693909317600160d01b92851692909202919091176001600160e81b0316600160e81b939092169290920217815590517f3248fab4375f32e0d851d39a71c0750d4652d98bcc7d32cec9d178c9824d796b91610f099187918791879190614224565b60405180910390a150505050565b601154600160301b900460ff1615610f425760405163769dd35360e11b815260040160405180910390fd5b806003541015610f6557604051631e9acf1760e31b815260040160405180910390fd5b8060036000828254610f77919061445a565b9250508190555080600a6000828254610f90919061445a565b9091555050600254610a35906001600160a01b031682612be1565b601154600090600160301b900460ff1615610fd95760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0385166000908152600660205260409020546001600160a01b031661101857604051630fb532db60e11b815260040160405180910390fd5b3360009081526005602090815260408083206001600160401b03808a168552925290912054168061106d57604051637800cff360e11b81526001600160401b0387166004820152336024820152604401610aaa565b60115461ffff9081169086161080611089575060c861ffff8616115b156110c05760115460405163539c34bb60e11b815261ffff8088166004830152909116602482015260c86044820152606401610aaa565b60115463ffffffff620100009091048116908516111561110e57601154604051637aebf00f60e11b815263ffffffff8087166004830152620100009092049091166024820152604401610aaa565b6101f463ffffffff84161115611147576040516311ce1afb60e21b815263ffffffff841660048201526101f46024820152604401610aaa565b60006111548260016143fc565b90506000806111658a338b86612c41565b604080516020810184905243918101919091526001600160401b038c16606082015263ffffffff808b166080830152891660a08201523360c0820152919350915060e00160408051601f19818403018152918152815160209283012060008581526010845282812091909155438152600890925290205460ff1661129357600c54600b54101580156111f957506000600b54115b1561129357600154600c546040516378c4059760e11b81524360048201526001600160a01b039092169163f1880b2e91906024016000604051808303818588803b15801561124657600080fd5b505af115801561125a573d6000803e3d6000fd5b5050505050600c54600b6000828254611273919061445a565b9091555050436000908152600860205260409020805460ff191660011790555b604080518381526020810183905261ffff8a168183015263ffffffff898116606083015288166080820152905133916001600160401b038c16918d917f63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772919081900360a00190a4503360009081526005602090815260408083206001600160401b03808d168552925290912080549190931667ffffffffffffffff199091161790915591505095945050505050565b601154600160301b900460ff161561136d5760405163769dd35360e11b815260040160405180910390fd5b336000908152600f60205260409020546001600160601b03821611156113a657604051631e9acf1760e31b815260040160405180910390fd5b336000908152600f6020526040812080546001600160601b03841692906113ce90849061445a565b92505081905550806001600160601b0316600a60008282546113f0919061445a565b909155506114099050826001600160601b038316612be1565b5050565b61141561292c565b6040805180820182526000916114449190849060029083908390808284376000920191909152506122a7915050565b6000818152600d60205260409020549091506001600160a01b03161561148057604051634a0b8fa760e01b815260048101829052602401610aaa565b6000818152600d6020908152604080832080546001600160a01b0319166001600160a01b038816908117909155600e805460018101825594527fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd909301849055518381527fe729ae16526293f74ade739043022254f1489f616295a25bf72dfb4511ed73b89101610d0a565b61151461292c565b61151e6000612cbb565b565b6001600160401b03821660009081526006602052604090205482906001600160a01b03168061156257604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b0382161461159657604051636c51fda960e11b81526001600160a01b0382166004820152602401610aaa565b601154600160301b900460ff16156115c15760405163769dd35360e11b815260040160405180910390fd5b6001600160401b038416600090815260066020526040902060020154606414156115fe576040516305a48e0f60e01b815260040160405180910390fd5b6001600160a01b03831660009081526005602090815260408083206001600160401b038089168552925290912054161561163757610b7d565b6001600160a01b03831660008181526005602090815260408083206001600160401b038916808552908352818420805467ffffffffffffffff19166001908117909155600684528285206002018054918201815585529383902090930180546001600160a01b031916851790555192835290917f43dc749a04ac8fb825cbd514f7c0e13f13bc6f2ee66043b76629d51776cff8e09101610b74565b601154600160301b900460ff16156116fd5760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0381166000908152600660205260409020546001600160a01b031661173c57604051630fb532db60e11b815260040160405180910390fd5b6001600160401b0381166000908152600660205260409020600101546001600160a01b031633146117a9576001600160401b0381166000908152600660205260409081902060010154905163d084e97560e01b81526001600160a01b039091166004820152602401610aaa565b6001600160401b0381166000818152600660209081526040918290208054336001600160a01b0319808316821784556001909301805490931690925583516001600160a01b03909116808252928101919091529092917f6f1dc65165ffffedfd8e507b4a0f1fcfdada045ed11f6c26ba27cedfe87802f091015b60405180910390a25050565b6001600160401b03821660009081526006602052604090205482906001600160a01b03168061187157604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b038216146118a557604051636c51fda960e11b81526001600160a01b0382166004820152602401610aaa565b601154600160301b900460ff16156118d05760405163769dd35360e11b815260040160405180910390fd5b6118d9846124c0565b156118f757604051631685ecdd60e31b815260040160405180910390fd5b6001600160a01b03831660009081526005602090815260408083206001600160401b0380891685529252909120541661195d57604051637800cff360e11b81526001600160401b03851660048201526001600160a01b0384166024820152604401610aaa565b6001600160401b0384166000908152600660209081526040808320600201805482518185028101850190935280835291929091908301828280156119ca57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116119ac575b505050505090506000600182516119e1919061445a565b905060005b8251811015611b0857856001600160a01b0316838281518110611a0b57611a0b614509565b60200260200101516001600160a01b03161415611af6576000838381518110611a3657611a36614509565b6020026020010151905080600660008a6001600160401b03166001600160401b031681526020019081526020016000206002018381548110611a7a57611a7a614509565b600091825260208083209190910180546001600160a01b0319166001600160a01b0394909416939093179092556001600160401b038a168152600690915260409020600201805480611ace57611ace6144f3565b600082815260209020810160001990810180546001600160a01b031916905501905550611b08565b80611b0081614471565b9150506119e6565b506001600160a01b03851660008181526005602090815260408083206001600160401b038b1680855290835292819020805467ffffffffffffffff191690555192835290917f182bff9831466789164ca77075fffd84916d35a8180ba73c27e45634549b445b910160405180910390a2505050505050565b601154600090600160301b900460ff1615611bae5760405163769dd35360e11b815260040160405180910390fd5b600980546001600160401b0316906000611bc78361448c565b82546101009290920a6001600160401b03818102199093169183160217909155600954169050600080604051908082528060200260200182016040528015611c19578160200160208202803683370190505b50604080518082018252600080825260208083018281526001600160401b0388168084526007835285842094518555905160019485015584516060810186523381528083018481528187018881529285526006845295909320835181546001600160a01b03199081166001600160a01b03928316178355965195820180549097169516949094179094559251805194955090939192611cc092600285019290910190613aff565b50506040513381526001600160401b03841691507f464722b4166576d3dcbba877b999bc35cf911f4eaf434b7eba68fa113951d0bf9060200160405180910390a250905090565b6001600160401b038116600090815260066020526040812054819081906060906001600160a01b0316611d4d57604051630fb532db60e11b815260040160405180910390fd5b6001600160401b0385166000908152600760209081526040808320805460019091015460068452938290208054600290910180548451818702810187019095528085529295946001600160a01b039092169390929091839190830182828015611ddf57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611dc1575b5050505050905093509350935093509193509193565b601154600090600160301b900460ff1615611e235760405163769dd35360e11b815260040160405180910390fd5b60005a90506000806000611e378787612d0b565b9250925092506000866060015163ffffffff166001600160401b03811115611e6157611e6161451f565b604051908082528060200260200182016040528015611e8a578160200160208202803683370190505b50905060005b876060015163ffffffff16811015611efe5760408051602081018590529081018290526060016040516020818303038152906040528051906020012060001c828281518110611ee157611ee1614509565b602090810291909101015280611ef681614471565b915050611e90565b50600083815260106020526040808220829055518190631fe543e360e01b90611f2d9087908690602401614303565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092526011805466ff0000000000001916600160301b179055908a015160808b0151919250600091611f959163ffffffff169084612f78565b6011805466ff000000000000191690556020808c0180516001600160401b03908116600090815260079093526040808420600190810154935190921684528320810180549495509193909290611fec9084906143e4565b9091555050601154600090612019908b90600160381b900463ffffffff166120138561266b565b3a612fc6565b6020808e01516001600160401b031660009081526007909152604090205490915081111561205a57604051631e9acf1760e31b815260040160405180910390fd5b6020808d01516001600160401b03166000908152600790915260408120805483929061208790849061445a565b909155505060045460649061209c908261445a565b6120a6908361443b565b6120b09190614427565b60008a8152600d60209081526040808320546001600160a01b03168352600f909152812080549091906120e49084906143e4565b90915550506004546064906120f9908361443b565b6121039190614427565b6003600082825461211491906143e4565b9091555050604080518881526020810183905284151581830152905189917f221ad2e5b871cead1dd7f75c2fb223c0cfa34bdc049a15f3f82a1f0e943e605a919081900360600190a299505050505050505050505b92915050565b601154600160301b900460ff161561219a5760405163769dd35360e11b815260040160405180910390fd5b600b805490349060006121ad83856143e4565b9091555050600b546040805183815260208101929092527ff09ca6cef38280114ac05d60379acf68fa7f85ea69ec4a8ed2a28626acafc06391015b60405180910390a150565b6121fb61292c565b6001600160a01b0382166122225760405163f0a7640b60e01b815260040160405180910390fd5b60648111156122445760405163197fcbd760e11b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b039390931692909217909155600455565b61227261292c565b600c8190556040518181527f4c42db8a799110fdd6a26148a21a5fbe4e581c926bccfd3b2d8a7f3aed4a87c8906020016121e8565b6000816040516020016122ba91906141b7565b604051602081830303815290604052805190602001209050919050565b6001600160401b03821660009081526006602052604090205482906001600160a01b03168061231957604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b0382161461234d57604051636c51fda960e11b81526001600160a01b0382166004820152602401610aaa565b601154600160301b900460ff16156123785760405163769dd35360e11b815260040160405180910390fd5b612381846124c0565b1561239f57604051631685ecdd60e31b815260040160405180910390fd5b610b7d8484612986565b6123b161292c565b600180546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527fa5a618fb1d2db8fcece4bcc171d33525bb774a19485486ba6f8ee0c50d59442a910160405180910390a15050565b61241a61292c565b600b5460009061242b90303161445a565b600a549091508181111561245c576040516354ced18160e11b81526004810182905260248101839052604401610aaa565b818110156124bb576000612470828461445a565b905061247c8482612be1565b604080516001600160a01b0386168152602081018390527f59bfc682b673f8cbf945f1e454df9334834abf7dfe7f92237ca29ecb9b4366009101610f09565b505050565b6001600160401b0381166000908152600660209081526040808320815160608101835281546001600160a01b039081168252600183015416818501526002820180548451818702810187018652818152879693958601939092919083018282801561255457602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612536575b505050505081525050905060005b8160400151518110156126615760005b600e5481101561264e576000612617600e838154811061259457612594614509565b9060005260206000200154856040015185815181106125b5576125b5614509565b60200260200101518860056000896040015189815181106125d8576125d8614509565b6020908102919091018101516001600160a01b0316825281810192909252604090810160009081206001600160401b03808f1683529352205416612c41565b506000818152601060205260409020549091501561263b5750600195945050505050565b508061264681614471565b915050612572565b508061265981614471565b915050612562565b5060009392505050565b604080516101208101825260125463ffffffff8082168352640100000000820481166020840152600160401b8204811693830193909352600160601b810483166060830152600160801b8104909216608082015262ffffff600160a01b8304811660a08301819052600160b81b8404821660c0840152600160d01b8404821660e0840152600160e81b90930416610100820152600091831161270e575192915050565b828160a0015162ffffff1610801561272f57508060c0015162ffffff168311155b1561273e576020015192915050565b828160c0015162ffffff1610801561275f57508060e0015162ffffff168311155b1561276e576040015192915050565b828160e0015162ffffff16108015612790575080610100015162ffffff168311155b1561279f576060015192915050565b6080015192915050565b601154600160301b900460ff16156127d45760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0381166000908152600660205260409020546001600160a01b031661281357604051630fb532db60e11b815260040160405180910390fd5b6001600160401b03811660009081526007602052604081208054916001600160601b033416919061284483856143e4565b92505081905550346001600160601b0316600a600082825461286691906143e4565b90915550506001600160401b0382167fd39ec07f4e209f627a4c427971473820dc129761ba28de8906bd56f57101d4f8826128a134826143e4565b60408051928352602083019190915201611823565b6128be61292c565b6001600160a01b0381166129235760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610aaa565b610a3581612cbb565b6000546001600160a01b0316331461151e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610aaa565b601154600160301b900460ff16156129b15760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0382166000908152600660209081526040808320815160608101835281546001600160a01b03908116825260018301541681850152600282018054845181870281018701865281815292959394860193830182828015612a4157602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612a23575b505050919092525050506001600160401b03841660009081526007602090815260408083208151808301909252805480835260019091015492820192909252929350905b836040015151811015612b06576005600085604001518381518110612aac57612aac614509565b6020908102919091018101516001600160a01b0316825281810192909252604090810160009081206001600160401b038a1682529092529020805467ffffffffffffffff1916905580612afe81614471565b915050612a85565b506001600160401b038516600090815260066020526040812080546001600160a01b03199081168255600182018054909116905590612b486002830182613b64565b50506001600160401b0385166000908152600760205260408120818155600101819055600a8054839290612b7d90849061445a565b90915550612b8d90508482612be1565b604080516001600160a01b0386168152602081018390526001600160401b038716917fe8ed5b475a5b5987aa9165e8731bb78043f39eee32ec5a1169a89e27fcd49815910160405180910390a25050505050565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114612c2e576040519150601f19603f3d011682016040523d82523d6000602084013e612c33565b606091505b50509050806124bb57600080fd5b6040805160208082018790526001600160a01b0395909516818301526001600160401b039384166060820152919092166080808301919091528251808303909101815260a08201835280519084012060c082019490945260e080820185905282518083039091018152610100909101909152805191012091565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806000612d1d85600001516122a7565b6000818152600d60205260409020549093506001600160a01b031680612d5957604051631dfd6e1360e21b815260048101859052602401610aaa565b6080860151604051612d78918691602001918252602082015260400190565b60408051601f1981840301815291815281516020928301206000818152601090935291205490935080612dbe57604051631b44092560e11b815260040160405180910390fd5b85516020808801516040808a015160608b015160808c01519251612e29968b9690959491019586526001600160401b03948516602087015292909316604085015263ffffffff90811660608501529190911660808301526001600160a01b031660a082015260c00190565b604051602081830303815290604052805190602001208114612e5e5760405163354a450b60e21b815260040160405180910390fd5b85516001600160401b03164080612f24576001548751604051631d2827a760e31b81526001600160401b0390911660048201526001600160a01b039091169063e9413d389060240160206040518083038186803b158015612ebe57600080fd5b505afa158015612ed2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ef69190613e64565b905080612f2457865160405163175dadad60e01b81526001600160401b039091166004820152602401610aaa565b6000886080015182604051602001612f46929190918252602082015260400190565b6040516020818303038152906040528051906020012060001c9050612f6b8982613021565b9450505050509250925092565b60005a611388811015612f8a57600080fd5b611388810390508460408204820311612fa257600080fd5b50823b612fae57600080fd5b60008083516020850160008789f190505b9392505050565b6000805a612fd487876143e4565b612fde919061445a565b612fe8908461443b565b9050600061300163ffffffff861664e8d4a5100061443b565b905061300d81836143e4565b6001600160601b0316979650505050505050565b60006130558360000151846020015185604001518660600151868860a001518960c001518a60e001518b610100015161308c565b6003836020015160405160200161306d9291906142ef565b60408051601f1981840301815291905280516020909101209392505050565b613095896132af565b6130e15760405162461bcd60e51b815260206004820152601a60248201527f7075626c6963206b6579206973206e6f74206f6e2063757276650000000000006044820152606401610aaa565b6130ea886132af565b61312e5760405162461bcd60e51b815260206004820152601560248201527467616d6d61206973206e6f74206f6e20637572766560581b6044820152606401610aaa565b613137836132af565b6131835760405162461bcd60e51b815260206004820152601d60248201527f6347616d6d615769746e657373206973206e6f74206f6e2063757276650000006044820152606401610aaa565b61318c826132af565b6131d85760405162461bcd60e51b815260206004820152601c60248201527f73486173685769746e657373206973206e6f74206f6e206375727665000000006044820152606401610aaa565b6131e4878a8887613372565b6132305760405162461bcd60e51b815260206004820152601960248201527f6164647228632a706b2b732a6729213d5f755769746e657373000000000000006044820152606401610aaa565b600061323c8a87613495565b9050600061324f898b878b8689896134f9565b90506000613260838d8d8a8661361e565b9050808a146132a15760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b210383937b7b360991b6044820152606401610aaa565b505050505050505050505050565b80516000906401000003d019116132fd5760405162461bcd60e51b8152602060048201526012602482015271696e76616c696420782d6f7264696e61746560701b6044820152606401610aaa565b60208201516401000003d0191161334b5760405162461bcd60e51b8152602060048201526012602482015271696e76616c696420792d6f7264696e61746560701b6044820152606401610aaa565b60208201516401000003d01990800961336b8360005b602002015161365e565b1492915050565b60006001600160a01b0382166133b85760405162461bcd60e51b815260206004820152600b60248201526a626164207769746e65737360a81b6044820152606401610aaa565b6020840151600090600116156133cf57601c6133d2565b601b5b9050600070014551231950b75fc4402da1732fc9bebe1985876000602002015109865170014551231950b75fc4402da1732fc9bebe19918203925060009190890987516040805160008082526020820180845287905260ff88169282019290925260608101929092526080820183905291925060019060a0016020604051602081039080840390855afa15801561346d573d6000803e3d6000fd5b5050604051601f1901516001600160a01b039081169088161495505050505050949350505050565b61349d613b82565b6134ca600184846040516020016134b693929190614196565b604051602081830303815290604052613682565b90505b6134d6816132af565b6121695780516040805160208101929092526134f291016134b6565b90506134cd565b613501613b82565b825186516401000003d01990819006910614156135605760405162461bcd60e51b815260206004820152601e60248201527f706f696e747320696e2073756d206d7573742062652064697374696e637400006044820152606401610aaa565b61356b8789886136d1565b6135b05760405162461bcd60e51b8152602060048201526016602482015275119a5c9cdd081b5d5b0818da1958dac819985a5b195960521b6044820152606401610aaa565b6135bb8486856136d1565b6136075760405162461bcd60e51b815260206004820152601760248201527f5365636f6e64206d756c20636865636b206661696c65640000000000000000006044820152606401610aaa565b6136128684846137f9565b98975050505050505050565b60006002868686858760405160200161363c96959493929190614137565b60408051601f1981840301815291905280516020909101209695505050505050565b6000806401000003d01980848509840990506401000003d019600782089392505050565b61368a613b82565b613693826138c0565b81526136a86136a3826000613361565b6138fb565b6020820181905260029006600114156136cc576020810180516401000003d0190390525b919050565b60008261370e5760405162461bcd60e51b815260206004820152600b60248201526a3d32b9379039b1b0b630b960a91b6044820152606401610aaa565b83516020850151600090613724906002906144b3565b1561373057601c613733565b601b5b9050600070014551231950b75fc4402da1732fc9bebe198387096040805160008082526020820180845281905260ff86169282019290925260608101869052608081018390529192509060019060a0016020604051602081039080840390855afa1580156137a5573d6000803e3d6000fd5b5050506020604051035190506000866040516020016137c49190614125565b60408051601f1981840301815291905280516020909101206001600160a01b0392831692169190911498975050505050505050565b613801613b82565b8351602080860151855191860151600093849384936138229390919061391b565b919450925090506401000003d0198582096001146138825760405162461bcd60e51b815260206004820152601960248201527f696e765a206d75737420626520696e7665727365206f66207a000000000000006044820152606401610aaa565b60405180604001604052806401000003d019806138a1576138a16144dd565b87860981526020016401000003d0198785099052979650505050505050565b805160208201205b6401000003d01981106136cc576040805160208082019390935281518082038401815290820190915280519101206138c8565b60006121698260026139146401000003d01960016143e4565b901c6139fb565b60008080600180826401000003d019896401000003d019038808905060006401000003d0198b6401000003d019038a089050600061395b83838585613a92565b909850905061396c88828e88613ab6565b909850905061397d88828c87613ab6565b909850905060006139908d878b85613ab6565b90985090506139a188828686613a92565b90985090506139b288828e89613ab6565b90985090508181146139e7576401000003d019818a0998506401000003d01982890997506401000003d01981830996506139eb565b8196505b5050505050509450945094915050565b600080613a06613ba0565b6020808252818101819052604082015260608101859052608081018490526401000003d01960a0820152613a38613bbe565b60208160c0846005600019fa925082613a885760405162461bcd60e51b81526020600482015260126024820152716269674d6f64457870206661696c7572652160701b6044820152606401610aaa565b5195945050505050565b6000806401000003d0198487096401000003d0198487099097909650945050505050565b600080806401000003d019878509905060006401000003d01987876401000003d019030990506401000003d0198183086401000003d01986890990999098509650505050505050565b828054828255906000526020600020908101928215613b54579160200282015b82811115613b5457825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190613b1f565b50613b60929150613bdc565b5090565b5080546000825590600052602060002090810190610a359190613bdc565b60405180604001604052806002906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b5b80821115613b605760008155600101613bdd565b80356001600160a01b03811681146136cc57600080fd5b806040810183101561216957600080fd5b600082601f830112613c2a57600080fd5b604051604081018181106001600160401b0382111715613c4c57613c4c61451f565b8060405250808385604086011115613c6357600080fd5b60005b6002811015613c85578135835260209283019290910190600101613c66565b509195945050505050565b600060a08284031215613ca257600080fd5b60405160a081018181106001600160401b0382111715613cc457613cc461451f565b604052905080613cd383613d59565b8152613ce160208401613d59565b6020820152613cf260408401613d45565b6040820152613d0360608401613d45565b6060820152613d1460808401613bf1565b60808201525092915050565b803561ffff811681146136cc57600080fd5b803562ffffff811681146136cc57600080fd5b803563ffffffff811681146136cc57600080fd5b80356001600160401b03811681146136cc57600080fd5b600060208284031215613d8257600080fd5b612fbf82613bf1565b60008060608385031215613d9e57600080fd5b613da783613bf1565b9150613db68460208501613c08565b90509250929050565b60008060408385031215613dd257600080fd5b613ddb83613bf1565b946020939093013593505050565b60008060408385031215613dfc57600080fd5b613e0583613bf1565b915060208301356001600160601b0381168114613e2157600080fd5b809150509250929050565b600060408284031215613e3e57600080fd5b612fbf8383613c08565b600060408284031215613e5a57600080fd5b612fbf8383613c19565b600060208284031215613e7657600080fd5b5051919050565b600080600080600060a08688031215613e9557600080fd5b85359450613ea560208701613d59565b9350613eb360408701613d20565b9250613ec160608701613d45565b9150613ecf60808701613d45565b90509295509295909350565b600080828403610240811215613ef057600080fd5b6101a080821215613f0057600080fd5b613f086143bb565b9150613f148686613c19565b8252613f238660408701613c19565b60208301526080850135604083015260a0850135606083015260c08501356080830152613f5260e08601613bf1565b60a0830152610100613f6687828801613c19565b60c0840152613f79876101408801613c19565b60e08401526101808601358184015250819350613f9886828701613c90565b925050509250929050565b600080600080848603610180811215613fbb57600080fd5b613fc486613d20565b9450613fd260208701613d45565b9350613fe060408701613d45565b925061012080605f1983011215613ff657600080fd5b613ffe6143bb565b915061400c60608801613d45565b825261401a60808801613d45565b602083015261402b60a08801613d45565b604083015261403c60c08801613d45565b606083015261404d60e08801613d45565b6080830152610100614060818901613d32565b60a0840152614070828901613d32565b60c08401526140826101408901613d32565b60e08401526140946101608901613d32565b9083015250939692955090935050565b6000602082840312156140b657600080fd5b5035919050565b6000602082840312156140cf57600080fd5b612fbf82613d59565b600080604083850312156140eb57600080fd5b6140f483613d59565b9150613db660208401613bf1565b8060005b6002811015610b7d578151845260209384019390910190600101614106565b61412f8183614102565b604001919050565b8681526141476020820187614102565b6141546060820186614102565b61416160a0820185614102565b61416e60e0820184614102565b60609190911b6bffffffffffffffffffffffff19166101208201526101340195945050505050565b8381526141a66020820184614102565b606081019190915260800192915050565b604081016121698284614102565b60006060820161ffff86168352602063ffffffff86168185015260606040850152818551808452608086019150828701935060005b81811015614216578451835293830193918301916001016141fa565b509098975050505050505050565b60006101808201905061ffff8616825263ffffffff808616602084015280851660408401528354818116606085015261426a60808501838360201c1663ffffffff169052565b61428160a08501838360401c1663ffffffff169052565b61429860c08501838360601c1663ffffffff169052565b6142af60e08501838360801c1663ffffffff169052565b62ffffff60a082901c811661010086015260b882901c811661012086015260d082901c1661014085015260e81c6101609093019290925295945050505050565b82815260608101612fbf6020830184614102565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561434457845183529383019391830191600101614328565b5090979650505050505050565b84815260208082018590526001600160a01b038481166040840152608060608401819052845190840181905260009285810192909160a0860190855b818110156143ab57855184168352948401949184019160010161438d565b50909a9950505050505050505050565b60405161012081016001600160401b03811182821017156143de576143de61451f565b60405290565b600082198211156143f7576143f76144c7565b500190565b60006001600160401b0380831681851680830382111561441e5761441e6144c7565b01949350505050565b600082614436576144366144dd565b500490565b6000816000190483118215151615614455576144556144c7565b500290565b60008282101561446c5761446c6144c7565b500390565b6000600019821415614485576144856144c7565b5060010190565b60006001600160401b03808316818114156144a9576144a96144c7565b6001019392505050565b6000826144c2576144c26144dd565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fdfea2646970667358221220119a51b9e3a6e198294494ca5d1018d1aa522d9be4880f69a71cd441a3cc49d264736f6c63430008060033000000000000000000000000fa559acc79e175b43d5c7e4d0371ff449d8bf5b7000000000000000000000000bb78efaaaf9223b4840ea7defdc379a13b16399b
Deployed ByteCode
0x6080604052600436106102715760003560e01c806385f2aef21161014f578063bc14edcf116100c1578063e4e1b45d1161007a578063e4e1b45d14610882578063e72f6e30146108a2578063e82ad7d4146108c2578063eccea3e2146108f2578063ee4201fe14610912578063f2fde38b1461092557600080fd5b8063bc14edcf14610748578063c3f909d414610768578063c8f7facc146107c1578063caf70c4a146107e1578063d7ae1d3014610801578063d7ca6f9b1461082157600080fd5b8063a0a19abd11610113578063a0a19abd146106a5578063a21a23e4146106c5578063a47c7696146106da578063af198b971461070a578063aff4fb231461072a578063b5a697a81461074057600080fd5b806385f2aef2146105f75780638da5cb5b1461062f57806390be10cc1461064d5780639f87fad71461066f578063a001ecdd1461068f57600080fd5b8063572727b0116101e85780636840c05e116101ac5780636840c05e1461050c57806369bcdb7d146105555780636f64f03f14610582578063715018a6146105a25780637341c10c146105b757806382359740146105d757600080fd5b8063572727b0146103f55780635d3b1d30146104155780635fbbc0d21461043557806364d51a2a146104d757806366316d8d146104ec57600080fd5b806312b583491161023a57806312b583491461033757806315c48b8414610356578063167957bd1461037e5780631d16919b1461039457806340d6bb82146103b45780634c6ad91c146103df57600080fd5b80620122911461027657806302bcc5b6146102a357806304c357cb146102c557806306bfa637146102e557806308821d5814610317575b600080fd5b34801561028257600080fd5b5061028b610945565b60405161029a939291906141c5565b60405180910390f35b3480156102af57600080fd5b506102c36102be3660046140bd565b6109c1565b005b3480156102d157600080fd5b506102c36102e03660046140d8565b610a38565b3480156102f157600080fd5b506009546001600160401b03165b6040516001600160401b03909116815260200161029a565b34801561032357600080fd5b506102c3610332366004613e2c565b610b83565b34801561034357600080fd5b50600a545b60405190815260200161029a565b34801561036257600080fd5b5061036b60c881565b60405161ffff909116815260200161029a565b34801561038a57600080fd5b5061034860035481565b3480156103a057600080fd5b506102c36103af366004613fa3565b610d17565b3480156103c057600080fd5b506103ca6101f481565b60405163ffffffff909116815260200161029a565b3480156103eb57600080fd5b50610348600b5481565b34801561040157600080fd5b506102c36104103660046140a4565b610f17565b34801561042157600080fd5b50610348610430366004613e7d565b610fab565b34801561044157600080fd5b506012546040805163ffffffff8084168252640100000000840481166020830152600160401b8404811692820192909252600160601b830482166060820152600160801b8304909116608082015262ffffff600160a01b8304811660a0830152600160b81b8304811660c0830152600160d01b8304811660e0830152600160e81b9092049091166101008201526101200161029a565b3480156104e357600080fd5b5061036b606481565b3480156104f857600080fd5b506102c3610507366004613de9565b611342565b34801561051857600080fd5b506105406105273660046140bd565b6007602052600090815260409020805460019091015482565b6040805192835260208301919091520161029a565b34801561056157600080fd5b506103486105703660046140a4565b60009081526010602052604090205490565b34801561058e57600080fd5b506102c361059d366004613d8b565b61140d565b3480156105ae57600080fd5b506102c361150c565b3480156105c357600080fd5b506102c36105d23660046140d8565b611520565b3480156105e357600080fd5b506102c36105f23660046140bd565b6116d2565b34801561060357600080fd5b50600254610617906001600160a01b031681565b6040516001600160a01b03909116815260200161029a565b34801561063b57600080fd5b506000546001600160a01b0316610617565b34801561065957600080fd5b50336000908152600f6020526040902054610348565b34801561067b57600080fd5b506102c361068a3660046140d8565b61182f565b34801561069b57600080fd5b5061034860045481565b3480156106b157600080fd5b50600154610617906001600160a01b031681565b3480156106d157600080fd5b506102ff611b80565b3480156106e657600080fd5b506106fa6106f53660046140bd565b611d07565b60405161029a9493929190614351565b34801561071657600080fd5b50610348610725366004613edb565b611df5565b34801561073657600080fd5b50610348600c5481565b6102c361216f565b34801561075457600080fd5b506102c3610763366004613dbf565b6121f3565b34801561077457600080fd5b5061079a60115461ffff81169163ffffffff620100008304811692600160381b90041690565b6040805161ffff909416845263ffffffff928316602085015291169082015260600161029a565b3480156107cd57600080fd5b506102c36107dc3660046140a4565b61226a565b3480156107ed57600080fd5b506103486107fc366004613e48565b6122a7565b34801561080d57600080fd5b506102c361081c3660046140d8565b6122d7565b34801561082d57600080fd5b5061086261083c3660046140bd565b600660205260009081526040902080546001909101546001600160a01b03918216911682565b604080516001600160a01b0393841681529290911660208301520161029a565b34801561088e57600080fd5b506102c361089d366004613d70565b6123a9565b3480156108ae57600080fd5b506102c36108bd366004613d70565b612412565b3480156108ce57600080fd5b506108e26108dd3660046140bd565b6124c0565b604051901515815260200161029a565b3480156108fe57600080fd5b506103ca61090d3660046140a4565b61266b565b6102c36109203660046140bd565b6127a9565b34801561093157600080fd5b506102c3610940366004613d70565b6128b6565b601154600e805460408051602080840282018101909252828152600094859460609461ffff8316946201000090930463ffffffff169391928391908301828280156109af57602002820191906000526020600020905b81548152602001906001019080831161099b575b50505050509050925092509250909192565b6109c961292c565b6001600160401b0381166000908152600660205260409020546001600160a01b0316610a0857604051630fb532db60e11b815260040160405180910390fd5b6001600160401b038116600090815260066020526040902054610a359082906001600160a01b0316612986565b50565b6001600160401b03821660009081526006602052604090205482906001600160a01b031680610a7a57604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b03821614610ab357604051636c51fda960e11b81526001600160a01b03821660048201526024015b60405180910390fd5b601154600160301b900460ff1615610ade5760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0384166000908152600660205260409020600101546001600160a01b03848116911614610b7d576001600160401b03841660008181526006602090815260409182902060010180546001600160a01b0319166001600160a01b0388169081179091558251338152918201527f69436ea6df009049404f564eff6622cd00522b0bd6a89efd9e52a355c4a879be91015b60405180910390a25b50505050565b610b8b61292c565b604080518082018252600091610bba9190849060029083908390808284376000920191909152506122a7915050565b6000818152600d60205260409020549091506001600160a01b031680610bf657604051631dfd6e1360e21b815260048101839052602401610aaa565b6000828152600d6020526040812080546001600160a01b03191690555b600e54811015610cce5782600e8281548110610c3157610c31614509565b90600052602060002001541415610cbc57600e805460009190610c569060019061445a565b81548110610c6657610c66614509565b9060005260206000200154905080600e8381548110610c8757610c87614509565b600091825260209091200155600e805480610ca457610ca46144f3565b60019003818190600052602060002001600090559055505b80610cc681614471565b915050610c13565b50806001600160a01b03167f72be339577868f868798bac2c93e52d6f034fef4689a9848996c14ebb7416c0d83604051610d0a91815260200190565b60405180910390a2505050565b610d1f61292c565b60c861ffff85161115610d595760405163539c34bb60e11b815261ffff851660048201819052602482015260c86044820152606401610aaa565b604080516080808201835261ffff871680835263ffffffff878116602080860182905260008688015288831660609687018190526011805465ffffffffffff191690951762010000909302929092176affffffffff0000000000001916600160381b909202919091179092558551601280549388015188880151968901519589015160a08a015160c08b015160e08c01516101008d015196881667ffffffffffffffff199099169890981764010000000094881694909402939093176fffffffffffffffff00000000000000001916600160401b9987169990990263ffffffff60601b191698909817600160601b978616979097029690961766ffffffffffffff60801b1916600160801b969094169590950262ffffff60a01b191692909217600160a01b62ffffff968716021765ffffffffffff60b81b1916600160b81b9486169490940262ffffff60d01b191693909317600160d01b92851692909202919091176001600160e81b0316600160e81b939092169290920217815590517f3248fab4375f32e0d851d39a71c0750d4652d98bcc7d32cec9d178c9824d796b91610f099187918791879190614224565b60405180910390a150505050565b601154600160301b900460ff1615610f425760405163769dd35360e11b815260040160405180910390fd5b806003541015610f6557604051631e9acf1760e31b815260040160405180910390fd5b8060036000828254610f77919061445a565b9250508190555080600a6000828254610f90919061445a565b9091555050600254610a35906001600160a01b031682612be1565b601154600090600160301b900460ff1615610fd95760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0385166000908152600660205260409020546001600160a01b031661101857604051630fb532db60e11b815260040160405180910390fd5b3360009081526005602090815260408083206001600160401b03808a168552925290912054168061106d57604051637800cff360e11b81526001600160401b0387166004820152336024820152604401610aaa565b60115461ffff9081169086161080611089575060c861ffff8616115b156110c05760115460405163539c34bb60e11b815261ffff8088166004830152909116602482015260c86044820152606401610aaa565b60115463ffffffff620100009091048116908516111561110e57601154604051637aebf00f60e11b815263ffffffff8087166004830152620100009092049091166024820152604401610aaa565b6101f463ffffffff84161115611147576040516311ce1afb60e21b815263ffffffff841660048201526101f46024820152604401610aaa565b60006111548260016143fc565b90506000806111658a338b86612c41565b604080516020810184905243918101919091526001600160401b038c16606082015263ffffffff808b166080830152891660a08201523360c0820152919350915060e00160408051601f19818403018152918152815160209283012060008581526010845282812091909155438152600890925290205460ff1661129357600c54600b54101580156111f957506000600b54115b1561129357600154600c546040516378c4059760e11b81524360048201526001600160a01b039092169163f1880b2e91906024016000604051808303818588803b15801561124657600080fd5b505af115801561125a573d6000803e3d6000fd5b5050505050600c54600b6000828254611273919061445a565b9091555050436000908152600860205260409020805460ff191660011790555b604080518381526020810183905261ffff8a168183015263ffffffff898116606083015288166080820152905133916001600160401b038c16918d917f63373d1c4696214b898952999c9aaec57dac1ee2723cec59bea6888f489a9772919081900360a00190a4503360009081526005602090815260408083206001600160401b03808d168552925290912080549190931667ffffffffffffffff199091161790915591505095945050505050565b601154600160301b900460ff161561136d5760405163769dd35360e11b815260040160405180910390fd5b336000908152600f60205260409020546001600160601b03821611156113a657604051631e9acf1760e31b815260040160405180910390fd5b336000908152600f6020526040812080546001600160601b03841692906113ce90849061445a565b92505081905550806001600160601b0316600a60008282546113f0919061445a565b909155506114099050826001600160601b038316612be1565b5050565b61141561292c565b6040805180820182526000916114449190849060029083908390808284376000920191909152506122a7915050565b6000818152600d60205260409020549091506001600160a01b03161561148057604051634a0b8fa760e01b815260048101829052602401610aaa565b6000818152600d6020908152604080832080546001600160a01b0319166001600160a01b038816908117909155600e805460018101825594527fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd909301849055518381527fe729ae16526293f74ade739043022254f1489f616295a25bf72dfb4511ed73b89101610d0a565b61151461292c565b61151e6000612cbb565b565b6001600160401b03821660009081526006602052604090205482906001600160a01b03168061156257604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b0382161461159657604051636c51fda960e11b81526001600160a01b0382166004820152602401610aaa565b601154600160301b900460ff16156115c15760405163769dd35360e11b815260040160405180910390fd5b6001600160401b038416600090815260066020526040902060020154606414156115fe576040516305a48e0f60e01b815260040160405180910390fd5b6001600160a01b03831660009081526005602090815260408083206001600160401b038089168552925290912054161561163757610b7d565b6001600160a01b03831660008181526005602090815260408083206001600160401b038916808552908352818420805467ffffffffffffffff19166001908117909155600684528285206002018054918201815585529383902090930180546001600160a01b031916851790555192835290917f43dc749a04ac8fb825cbd514f7c0e13f13bc6f2ee66043b76629d51776cff8e09101610b74565b601154600160301b900460ff16156116fd5760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0381166000908152600660205260409020546001600160a01b031661173c57604051630fb532db60e11b815260040160405180910390fd5b6001600160401b0381166000908152600660205260409020600101546001600160a01b031633146117a9576001600160401b0381166000908152600660205260409081902060010154905163d084e97560e01b81526001600160a01b039091166004820152602401610aaa565b6001600160401b0381166000818152600660209081526040918290208054336001600160a01b0319808316821784556001909301805490931690925583516001600160a01b03909116808252928101919091529092917f6f1dc65165ffffedfd8e507b4a0f1fcfdada045ed11f6c26ba27cedfe87802f091015b60405180910390a25050565b6001600160401b03821660009081526006602052604090205482906001600160a01b03168061187157604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b038216146118a557604051636c51fda960e11b81526001600160a01b0382166004820152602401610aaa565b601154600160301b900460ff16156118d05760405163769dd35360e11b815260040160405180910390fd5b6118d9846124c0565b156118f757604051631685ecdd60e31b815260040160405180910390fd5b6001600160a01b03831660009081526005602090815260408083206001600160401b0380891685529252909120541661195d57604051637800cff360e11b81526001600160401b03851660048201526001600160a01b0384166024820152604401610aaa565b6001600160401b0384166000908152600660209081526040808320600201805482518185028101850190935280835291929091908301828280156119ca57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116119ac575b505050505090506000600182516119e1919061445a565b905060005b8251811015611b0857856001600160a01b0316838281518110611a0b57611a0b614509565b60200260200101516001600160a01b03161415611af6576000838381518110611a3657611a36614509565b6020026020010151905080600660008a6001600160401b03166001600160401b031681526020019081526020016000206002018381548110611a7a57611a7a614509565b600091825260208083209190910180546001600160a01b0319166001600160a01b0394909416939093179092556001600160401b038a168152600690915260409020600201805480611ace57611ace6144f3565b600082815260209020810160001990810180546001600160a01b031916905501905550611b08565b80611b0081614471565b9150506119e6565b506001600160a01b03851660008181526005602090815260408083206001600160401b038b1680855290835292819020805467ffffffffffffffff191690555192835290917f182bff9831466789164ca77075fffd84916d35a8180ba73c27e45634549b445b910160405180910390a2505050505050565b601154600090600160301b900460ff1615611bae5760405163769dd35360e11b815260040160405180910390fd5b600980546001600160401b0316906000611bc78361448c565b82546101009290920a6001600160401b03818102199093169183160217909155600954169050600080604051908082528060200260200182016040528015611c19578160200160208202803683370190505b50604080518082018252600080825260208083018281526001600160401b0388168084526007835285842094518555905160019485015584516060810186523381528083018481528187018881529285526006845295909320835181546001600160a01b03199081166001600160a01b03928316178355965195820180549097169516949094179094559251805194955090939192611cc092600285019290910190613aff565b50506040513381526001600160401b03841691507f464722b4166576d3dcbba877b999bc35cf911f4eaf434b7eba68fa113951d0bf9060200160405180910390a250905090565b6001600160401b038116600090815260066020526040812054819081906060906001600160a01b0316611d4d57604051630fb532db60e11b815260040160405180910390fd5b6001600160401b0385166000908152600760209081526040808320805460019091015460068452938290208054600290910180548451818702810187019095528085529295946001600160a01b039092169390929091839190830182828015611ddf57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611dc1575b5050505050905093509350935093509193509193565b601154600090600160301b900460ff1615611e235760405163769dd35360e11b815260040160405180910390fd5b60005a90506000806000611e378787612d0b565b9250925092506000866060015163ffffffff166001600160401b03811115611e6157611e6161451f565b604051908082528060200260200182016040528015611e8a578160200160208202803683370190505b50905060005b876060015163ffffffff16811015611efe5760408051602081018590529081018290526060016040516020818303038152906040528051906020012060001c828281518110611ee157611ee1614509565b602090810291909101015280611ef681614471565b915050611e90565b50600083815260106020526040808220829055518190631fe543e360e01b90611f2d9087908690602401614303565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092526011805466ff0000000000001916600160301b179055908a015160808b0151919250600091611f959163ffffffff169084612f78565b6011805466ff000000000000191690556020808c0180516001600160401b03908116600090815260079093526040808420600190810154935190921684528320810180549495509193909290611fec9084906143e4565b9091555050601154600090612019908b90600160381b900463ffffffff166120138561266b565b3a612fc6565b6020808e01516001600160401b031660009081526007909152604090205490915081111561205a57604051631e9acf1760e31b815260040160405180910390fd5b6020808d01516001600160401b03166000908152600790915260408120805483929061208790849061445a565b909155505060045460649061209c908261445a565b6120a6908361443b565b6120b09190614427565b60008a8152600d60209081526040808320546001600160a01b03168352600f909152812080549091906120e49084906143e4565b90915550506004546064906120f9908361443b565b6121039190614427565b6003600082825461211491906143e4565b9091555050604080518881526020810183905284151581830152905189917f221ad2e5b871cead1dd7f75c2fb223c0cfa34bdc049a15f3f82a1f0e943e605a919081900360600190a299505050505050505050505b92915050565b601154600160301b900460ff161561219a5760405163769dd35360e11b815260040160405180910390fd5b600b805490349060006121ad83856143e4565b9091555050600b546040805183815260208101929092527ff09ca6cef38280114ac05d60379acf68fa7f85ea69ec4a8ed2a28626acafc06391015b60405180910390a150565b6121fb61292c565b6001600160a01b0382166122225760405163f0a7640b60e01b815260040160405180910390fd5b60648111156122445760405163197fcbd760e11b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b039390931692909217909155600455565b61227261292c565b600c8190556040518181527f4c42db8a799110fdd6a26148a21a5fbe4e581c926bccfd3b2d8a7f3aed4a87c8906020016121e8565b6000816040516020016122ba91906141b7565b604051602081830303815290604052805190602001209050919050565b6001600160401b03821660009081526006602052604090205482906001600160a01b03168061231957604051630fb532db60e11b815260040160405180910390fd5b336001600160a01b0382161461234d57604051636c51fda960e11b81526001600160a01b0382166004820152602401610aaa565b601154600160301b900460ff16156123785760405163769dd35360e11b815260040160405180910390fd5b612381846124c0565b1561239f57604051631685ecdd60e31b815260040160405180910390fd5b610b7d8484612986565b6123b161292c565b600180546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527fa5a618fb1d2db8fcece4bcc171d33525bb774a19485486ba6f8ee0c50d59442a910160405180910390a15050565b61241a61292c565b600b5460009061242b90303161445a565b600a549091508181111561245c576040516354ced18160e11b81526004810182905260248101839052604401610aaa565b818110156124bb576000612470828461445a565b905061247c8482612be1565b604080516001600160a01b0386168152602081018390527f59bfc682b673f8cbf945f1e454df9334834abf7dfe7f92237ca29ecb9b4366009101610f09565b505050565b6001600160401b0381166000908152600660209081526040808320815160608101835281546001600160a01b039081168252600183015416818501526002820180548451818702810187018652818152879693958601939092919083018282801561255457602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612536575b505050505081525050905060005b8160400151518110156126615760005b600e5481101561264e576000612617600e838154811061259457612594614509565b9060005260206000200154856040015185815181106125b5576125b5614509565b60200260200101518860056000896040015189815181106125d8576125d8614509565b6020908102919091018101516001600160a01b0316825281810192909252604090810160009081206001600160401b03808f1683529352205416612c41565b506000818152601060205260409020549091501561263b5750600195945050505050565b508061264681614471565b915050612572565b508061265981614471565b915050612562565b5060009392505050565b604080516101208101825260125463ffffffff8082168352640100000000820481166020840152600160401b8204811693830193909352600160601b810483166060830152600160801b8104909216608082015262ffffff600160a01b8304811660a08301819052600160b81b8404821660c0840152600160d01b8404821660e0840152600160e81b90930416610100820152600091831161270e575192915050565b828160a0015162ffffff1610801561272f57508060c0015162ffffff168311155b1561273e576020015192915050565b828160c0015162ffffff1610801561275f57508060e0015162ffffff168311155b1561276e576040015192915050565b828160e0015162ffffff16108015612790575080610100015162ffffff168311155b1561279f576060015192915050565b6080015192915050565b601154600160301b900460ff16156127d45760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0381166000908152600660205260409020546001600160a01b031661281357604051630fb532db60e11b815260040160405180910390fd5b6001600160401b03811660009081526007602052604081208054916001600160601b033416919061284483856143e4565b92505081905550346001600160601b0316600a600082825461286691906143e4565b90915550506001600160401b0382167fd39ec07f4e209f627a4c427971473820dc129761ba28de8906bd56f57101d4f8826128a134826143e4565b60408051928352602083019190915201611823565b6128be61292c565b6001600160a01b0381166129235760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610aaa565b610a3581612cbb565b6000546001600160a01b0316331461151e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610aaa565b601154600160301b900460ff16156129b15760405163769dd35360e11b815260040160405180910390fd5b6001600160401b0382166000908152600660209081526040808320815160608101835281546001600160a01b03908116825260018301541681850152600282018054845181870281018701865281815292959394860193830182828015612a4157602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612a23575b505050919092525050506001600160401b03841660009081526007602090815260408083208151808301909252805480835260019091015492820192909252929350905b836040015151811015612b06576005600085604001518381518110612aac57612aac614509565b6020908102919091018101516001600160a01b0316825281810192909252604090810160009081206001600160401b038a1682529092529020805467ffffffffffffffff1916905580612afe81614471565b915050612a85565b506001600160401b038516600090815260066020526040812080546001600160a01b03199081168255600182018054909116905590612b486002830182613b64565b50506001600160401b0385166000908152600760205260408120818155600101819055600a8054839290612b7d90849061445a565b90915550612b8d90508482612be1565b604080516001600160a01b0386168152602081018390526001600160401b038716917fe8ed5b475a5b5987aa9165e8731bb78043f39eee32ec5a1169a89e27fcd49815910160405180910390a25050505050565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114612c2e576040519150601f19603f3d011682016040523d82523d6000602084013e612c33565b606091505b50509050806124bb57600080fd5b6040805160208082018790526001600160a01b0395909516818301526001600160401b039384166060820152919092166080808301919091528251808303909101815260a08201835280519084012060c082019490945260e080820185905282518083039091018152610100909101909152805191012091565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806000612d1d85600001516122a7565b6000818152600d60205260409020549093506001600160a01b031680612d5957604051631dfd6e1360e21b815260048101859052602401610aaa565b6080860151604051612d78918691602001918252602082015260400190565b60408051601f1981840301815291815281516020928301206000818152601090935291205490935080612dbe57604051631b44092560e11b815260040160405180910390fd5b85516020808801516040808a015160608b015160808c01519251612e29968b9690959491019586526001600160401b03948516602087015292909316604085015263ffffffff90811660608501529190911660808301526001600160a01b031660a082015260c00190565b604051602081830303815290604052805190602001208114612e5e5760405163354a450b60e21b815260040160405180910390fd5b85516001600160401b03164080612f24576001548751604051631d2827a760e31b81526001600160401b0390911660048201526001600160a01b039091169063e9413d389060240160206040518083038186803b158015612ebe57600080fd5b505afa158015612ed2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612ef69190613e64565b905080612f2457865160405163175dadad60e01b81526001600160401b039091166004820152602401610aaa565b6000886080015182604051602001612f46929190918252602082015260400190565b6040516020818303038152906040528051906020012060001c9050612f6b8982613021565b9450505050509250925092565b60005a611388811015612f8a57600080fd5b611388810390508460408204820311612fa257600080fd5b50823b612fae57600080fd5b60008083516020850160008789f190505b9392505050565b6000805a612fd487876143e4565b612fde919061445a565b612fe8908461443b565b9050600061300163ffffffff861664e8d4a5100061443b565b905061300d81836143e4565b6001600160601b0316979650505050505050565b60006130558360000151846020015185604001518660600151868860a001518960c001518a60e001518b610100015161308c565b6003836020015160405160200161306d9291906142ef565b60408051601f1981840301815291905280516020909101209392505050565b613095896132af565b6130e15760405162461bcd60e51b815260206004820152601a60248201527f7075626c6963206b6579206973206e6f74206f6e2063757276650000000000006044820152606401610aaa565b6130ea886132af565b61312e5760405162461bcd60e51b815260206004820152601560248201527467616d6d61206973206e6f74206f6e20637572766560581b6044820152606401610aaa565b613137836132af565b6131835760405162461bcd60e51b815260206004820152601d60248201527f6347616d6d615769746e657373206973206e6f74206f6e2063757276650000006044820152606401610aaa565b61318c826132af565b6131d85760405162461bcd60e51b815260206004820152601c60248201527f73486173685769746e657373206973206e6f74206f6e206375727665000000006044820152606401610aaa565b6131e4878a8887613372565b6132305760405162461bcd60e51b815260206004820152601960248201527f6164647228632a706b2b732a6729213d5f755769746e657373000000000000006044820152606401610aaa565b600061323c8a87613495565b9050600061324f898b878b8689896134f9565b90506000613260838d8d8a8661361e565b9050808a146132a15760405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b210383937b7b360991b6044820152606401610aaa565b505050505050505050505050565b80516000906401000003d019116132fd5760405162461bcd60e51b8152602060048201526012602482015271696e76616c696420782d6f7264696e61746560701b6044820152606401610aaa565b60208201516401000003d0191161334b5760405162461bcd60e51b8152602060048201526012602482015271696e76616c696420792d6f7264696e61746560701b6044820152606401610aaa565b60208201516401000003d01990800961336b8360005b602002015161365e565b1492915050565b60006001600160a01b0382166133b85760405162461bcd60e51b815260206004820152600b60248201526a626164207769746e65737360a81b6044820152606401610aaa565b6020840151600090600116156133cf57601c6133d2565b601b5b9050600070014551231950b75fc4402da1732fc9bebe1985876000602002015109865170014551231950b75fc4402da1732fc9bebe19918203925060009190890987516040805160008082526020820180845287905260ff88169282019290925260608101929092526080820183905291925060019060a0016020604051602081039080840390855afa15801561346d573d6000803e3d6000fd5b5050604051601f1901516001600160a01b039081169088161495505050505050949350505050565b61349d613b82565b6134ca600184846040516020016134b693929190614196565b604051602081830303815290604052613682565b90505b6134d6816132af565b6121695780516040805160208101929092526134f291016134b6565b90506134cd565b613501613b82565b825186516401000003d01990819006910614156135605760405162461bcd60e51b815260206004820152601e60248201527f706f696e747320696e2073756d206d7573742062652064697374696e637400006044820152606401610aaa565b61356b8789886136d1565b6135b05760405162461bcd60e51b8152602060048201526016602482015275119a5c9cdd081b5d5b0818da1958dac819985a5b195960521b6044820152606401610aaa565b6135bb8486856136d1565b6136075760405162461bcd60e51b815260206004820152601760248201527f5365636f6e64206d756c20636865636b206661696c65640000000000000000006044820152606401610aaa565b6136128684846137f9565b98975050505050505050565b60006002868686858760405160200161363c96959493929190614137565b60408051601f1981840301815291905280516020909101209695505050505050565b6000806401000003d01980848509840990506401000003d019600782089392505050565b61368a613b82565b613693826138c0565b81526136a86136a3826000613361565b6138fb565b6020820181905260029006600114156136cc576020810180516401000003d0190390525b919050565b60008261370e5760405162461bcd60e51b815260206004820152600b60248201526a3d32b9379039b1b0b630b960a91b6044820152606401610aaa565b83516020850151600090613724906002906144b3565b1561373057601c613733565b601b5b9050600070014551231950b75fc4402da1732fc9bebe198387096040805160008082526020820180845281905260ff86169282019290925260608101869052608081018390529192509060019060a0016020604051602081039080840390855afa1580156137a5573d6000803e3d6000fd5b5050506020604051035190506000866040516020016137c49190614125565b60408051601f1981840301815291905280516020909101206001600160a01b0392831692169190911498975050505050505050565b613801613b82565b8351602080860151855191860151600093849384936138229390919061391b565b919450925090506401000003d0198582096001146138825760405162461bcd60e51b815260206004820152601960248201527f696e765a206d75737420626520696e7665727365206f66207a000000000000006044820152606401610aaa565b60405180604001604052806401000003d019806138a1576138a16144dd565b87860981526020016401000003d0198785099052979650505050505050565b805160208201205b6401000003d01981106136cc576040805160208082019390935281518082038401815290820190915280519101206138c8565b60006121698260026139146401000003d01960016143e4565b901c6139fb565b60008080600180826401000003d019896401000003d019038808905060006401000003d0198b6401000003d019038a089050600061395b83838585613a92565b909850905061396c88828e88613ab6565b909850905061397d88828c87613ab6565b909850905060006139908d878b85613ab6565b90985090506139a188828686613a92565b90985090506139b288828e89613ab6565b90985090508181146139e7576401000003d019818a0998506401000003d01982890997506401000003d01981830996506139eb565b8196505b5050505050509450945094915050565b600080613a06613ba0565b6020808252818101819052604082015260608101859052608081018490526401000003d01960a0820152613a38613bbe565b60208160c0846005600019fa925082613a885760405162461bcd60e51b81526020600482015260126024820152716269674d6f64457870206661696c7572652160701b6044820152606401610aaa565b5195945050505050565b6000806401000003d0198487096401000003d0198487099097909650945050505050565b600080806401000003d019878509905060006401000003d01987876401000003d019030990506401000003d0198183086401000003d01986890990999098509650505050505050565b828054828255906000526020600020908101928215613b54579160200282015b82811115613b5457825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190613b1f565b50613b60929150613bdc565b5090565b5080546000825590600052602060002090810190610a359190613bdc565b60405180604001604052806002906020820280368337509192915050565b6040518060c001604052806006906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b5b80821115613b605760008155600101613bdd565b80356001600160a01b03811681146136cc57600080fd5b806040810183101561216957600080fd5b600082601f830112613c2a57600080fd5b604051604081018181106001600160401b0382111715613c4c57613c4c61451f565b8060405250808385604086011115613c6357600080fd5b60005b6002811015613c85578135835260209283019290910190600101613c66565b509195945050505050565b600060a08284031215613ca257600080fd5b60405160a081018181106001600160401b0382111715613cc457613cc461451f565b604052905080613cd383613d59565b8152613ce160208401613d59565b6020820152613cf260408401613d45565b6040820152613d0360608401613d45565b6060820152613d1460808401613bf1565b60808201525092915050565b803561ffff811681146136cc57600080fd5b803562ffffff811681146136cc57600080fd5b803563ffffffff811681146136cc57600080fd5b80356001600160401b03811681146136cc57600080fd5b600060208284031215613d8257600080fd5b612fbf82613bf1565b60008060608385031215613d9e57600080fd5b613da783613bf1565b9150613db68460208501613c08565b90509250929050565b60008060408385031215613dd257600080fd5b613ddb83613bf1565b946020939093013593505050565b60008060408385031215613dfc57600080fd5b613e0583613bf1565b915060208301356001600160601b0381168114613e2157600080fd5b809150509250929050565b600060408284031215613e3e57600080fd5b612fbf8383613c08565b600060408284031215613e5a57600080fd5b612fbf8383613c19565b600060208284031215613e7657600080fd5b5051919050565b600080600080600060a08688031215613e9557600080fd5b85359450613ea560208701613d59565b9350613eb360408701613d20565b9250613ec160608701613d45565b9150613ecf60808701613d45565b90509295509295909350565b600080828403610240811215613ef057600080fd5b6101a080821215613f0057600080fd5b613f086143bb565b9150613f148686613c19565b8252613f238660408701613c19565b60208301526080850135604083015260a0850135606083015260c08501356080830152613f5260e08601613bf1565b60a0830152610100613f6687828801613c19565b60c0840152613f79876101408801613c19565b60e08401526101808601358184015250819350613f9886828701613c90565b925050509250929050565b600080600080848603610180811215613fbb57600080fd5b613fc486613d20565b9450613fd260208701613d45565b9350613fe060408701613d45565b925061012080605f1983011215613ff657600080fd5b613ffe6143bb565b915061400c60608801613d45565b825261401a60808801613d45565b602083015261402b60a08801613d45565b604083015261403c60c08801613d45565b606083015261404d60e08801613d45565b6080830152610100614060818901613d32565b60a0840152614070828901613d32565b60c08401526140826101408901613d32565b60e08401526140946101608901613d32565b9083015250939692955090935050565b6000602082840312156140b657600080fd5b5035919050565b6000602082840312156140cf57600080fd5b612fbf82613d59565b600080604083850312156140eb57600080fd5b6140f483613d59565b9150613db660208401613bf1565b8060005b6002811015610b7d578151845260209384019390910190600101614106565b61412f8183614102565b604001919050565b8681526141476020820187614102565b6141546060820186614102565b61416160a0820185614102565b61416e60e0820184614102565b60609190911b6bffffffffffffffffffffffff19166101208201526101340195945050505050565b8381526141a66020820184614102565b606081019190915260800192915050565b604081016121698284614102565b60006060820161ffff86168352602063ffffffff86168185015260606040850152818551808452608086019150828701935060005b81811015614216578451835293830193918301916001016141fa565b509098975050505050505050565b60006101808201905061ffff8616825263ffffffff808616602084015280851660408401528354818116606085015261426a60808501838360201c1663ffffffff169052565b61428160a08501838360401c1663ffffffff169052565b61429860c08501838360601c1663ffffffff169052565b6142af60e08501838360801c1663ffffffff169052565b62ffffff60a082901c811661010086015260b882901c811661012086015260d082901c1661014085015260e81c6101609093019290925295945050505050565b82815260608101612fbf6020830184614102565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561434457845183529383019391830191600101614328565b5090979650505050505050565b84815260208082018590526001600160a01b038481166040840152608060608401819052845190840181905260009285810192909160a0860190855b818110156143ab57855184168352948401949184019160010161438d565b50909a9950505050505050505050565b60405161012081016001600160401b03811182821017156143de576143de61451f565b60405290565b600082198211156143f7576143f76144c7565b500190565b60006001600160401b0380831681851680830382111561441e5761441e6144c7565b01949350505050565b600082614436576144366144dd565b500490565b6000816000190483118215151615614455576144556144c7565b500290565b60008282101561446c5761446c6144c7565b500390565b6000600019821415614485576144856144c7565b5060010190565b60006001600160401b03808316818114156144a9576144a96144c7565b6001019392505050565b6000826144c2576144c26144dd565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fdfea2646970667358221220119a51b9e3a6e198294494ca5d1018d1aa522d9be4880f69a71cd441a3cc49d264736f6c63430008060033