false
false

Contract Address Details

0x6ab3bb996290033a8161a0fbedad940bb0c1a60a

Contract Name
Distributions
Creator
0x3455fa–f3666d at 0x40f0d6–702935
Balance
0 ETH ( )
Tokens
Fetching tokens...
Transactions
0 Transactions
Transfers
0 Transfers
Gas Used
Fetching gas used...
Last Balance Update
53082087
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
Contract name:
Distributions




Optimization enabled
true
Compiler version
v0.8.12+commit.f00d7308




Optimization runs
10
Verified at
2023-08-24T23:26:33.523000Z

contracts/Distributions.sol

/*
    SPDX-License-Identifier: Apache-2.0

    Copyright 2021 Reddit, Inc

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "./ISubredditPoints.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";
import "./Compress.sol";

/**
 * ERRORS
 */
/// @notice initial_supply must be <= MAX_ROUND_SUPPLY in constructor params.
error Distributions__InitialSupplyMoreThanMaxRoundSupply();
/// @notice initial_karma must be > 0 in constructor params.
error Distributions__InitialKarmaShouldBeMoreThanZero();
/// @notice next supply should be between 0 and MAX_ROUND_SUPPLY in constructor params.
error Distributions__NextSupplyShouldBeBetweenZeroAndMaxRoundSupply();
/// @notice karma source != address(0)
error Distributions__KarmaSourceShouldNotBeZeroAddress();
/// @notice owner != address(0) in constructor params.
error Distributions__OwnerShouldNotBeZeroAddress();
/// @notice each shared owner should have their ownership percentage when passing the constructor params.
error Distributions__SharedOwnersArrayMustBeSameLengthAsPercentages();
/// @notice subredditPoints contract can't be address(0)
error Distributions__SubredditPointsShouldNotBeZeroAddress();
/// @notice Claim must be signed by karma source or prev karma source.
error Distributions__ClaimIsNotSignedByKarmaSource();
/// @notice The function call only be called by Karma Source (or prev karma source)
error Distributions__CanOnlyBeCalledByKarmaSource();
/// @notice no airdrop input has been passed to airdrop()
error Distributions__AirdropIsEmpty();
/// @notice the particular airdrop batch is empty
error Distributions__AirdropBatchIsEmpty();
/// @notice airdrop batch type is neither ACCOUNTS, ADDRESSES, GROUPED_ACCOUNTS or GROUPED_ADDRESSES
error Distributions__UnknownAirdropBatchType();
/// @notice if airdrop receiving account is either address(0) or not in _accountAddresses map.
error Distributions__AccountIdNotFound();
/// @notice generic error if address is zero.
error Distributions__AddressShouldNotBeZero();
/// @notice if user's karma is 0 - then no points can be minted.
error Distributions__KarmaShouldNotBeZero();
/// @notice if user has already claimed points for this round
error Distributions__ThisRoundPointsAreAlreadyClaimed();
/// @notice if round > lastRound i.e. if round has not been finalised yet.
error Distributions__TooEarlyToClaimThisRound();
/// @notice if the time window for claiming points for the round has expired.
error Distributions__TooLateToClaimThisRound();
/// @notice only owner can claim points from a round that is not the first round the user can claim.
error Distributions__OnlyOwnerCanClaimRoundThatIsNotEarliest();
/// @notice the distribution round has 0 available points
error Distributions__NoPointsToClaimThisRound();
/// @notice the distribution round has 0 total karma
error Distributions__NoKarmaInThisRound();
/// @notice the points a user can claim this round seems to be 0 due to their low karma count.
error Distributions__UserKarmaTooLowToClaimPoints();
/// @notice can only advance to round in increments.
error Distributions__RoundShouldBeIncrementallyIncreasing();
/// @notice total karma in the next round should be greater than 0
error Distributions__TotalKarmaShouldBeGreaterThanZero();
/// @notice any percentage must be lesser than PERCENT_PRECISION
error Distributions__PercentShouldBeLessThanPERCENT_PRECISION();
/// @notice if shared owner's percent ownership is 0 or num of shared owners  >= MAX_SHARED_OWNERS
error Distributions__SharedOwnersLimitReached(uint256 numSharedOwners, uint256 MAX_SHARED_OWNERS);
/// @notice if total percentage ownership across all shared owners is >= PERCENT_PRECISION
error Distributions__CantShareAll100PercentOfPoints(uint256 total);


/**
 * @title Distributions
 * @notice Implements logic to mint points every round and tracks points receivers such as shared owners
 * (get's cut from all minted points), users rounds and their account IDs
 */
contract Distributions is Initializable, OwnableUpgradeable {

    struct SharedOwner {
        address account;
        uint256 percent; // e.g. 30% percent = 30 * percentPrecision/100
    }

    struct DistributionRound {
        uint256 availablePoints;
        uint256 sharedOwnersAvailablePoints;
        uint256 totalKarma;
    }

    event SharedOwnerUpdated(
        address indexed _from,
        address indexed _to,
        uint256 _percent
    );

    event AdvanceRound(uint256 round, uint256 totalPoints, uint256 sharedOwnersPoints);
    event ClaimPoints(uint256 round, address indexed user, uint256 karma, uint256 points);
    event KarmaSourceUpdated(address karmaSource, address prevKarmaSource);
    event SupplyDecayPercentUpdated(uint256 supplyDecayPercent);
    event RoundsBeforeExpirationUpdated(uint256 roundsBeforeExpiration);
    event AccountRegistered(address indexed user, uint256 indexed accountId);

    enum AirdropBatchType {ACCOUNTS, ADDRESSES, GROUPED_ACCOUNTS, GROUPED_ADDRESSES}

    using AddressUpgradeable for address;
    using BytesLib for bytes;
    using Compress for bytes;

    // ------------------------------------------------------------------------------------
    // VARIABLES BLOCK, MAKE SURE ONLY ADD TO THE END

    ISubredditPoints public subredditPoints;
    address public karmaSource;
    string public subreddit;
    string internal _subredditLowerCase;
    uint256 public lastRound;
    // maps round number to round data
    mapping(uint256 => DistributionRound) internal _distributionRounds;
    // maps account to next claimable round
    mapping(address => uint256) internal _claimableRounds;

    // when sharing percentage, the least possible share is 1/percentPrecision
    uint256 public constant PERCENT_PRECISION = 1000000;
    uint256 public constant MAX_ROUND_SUPPLY = 10 ** 11 * 10 ** 18; // max is 100 bln, to prevent overflows

    // 1 shared owner ~ 35k gas + 250k gas for other ops in advanceToRound
    // so we limit to (8M - 250k)/35k = 221 maximum shared owners
    uint256 public constant MAX_SHARED_OWNERS = 200;

    uint256 public initialSupply;
    uint256 public roundsBeforeExpiration;
    uint256 public nextSupply;
    uint256 public supplyDecayPercent;

    // those owners if exists will get proportion of minted points according to value in percentage
    SharedOwner[] public sharedOwners;
    uint256 internal _prevRoundSupply;       // supply in a prev round
    uint256 internal _prevClaimed;           // total claimed in a prev round

    // Previous karmaSource signer. Used when rotating karmaSource key to enable
    // previous signer to still be valid for a while.
    address public prevKarmaSource;

    // maps account addresses to account ids
    mapping(uint256 => address) internal _accountAddresses;
    mapping(address => uint256) internal _accountIds;
    uint256 internal _lastAccountId;

    uint256 internal _prevOwnersShare;

    struct Params { //struct to prevent stack too deep errors
        address owner_;
        address subredditPoints_; // ISubredditPoints + IERC20 token contract address
        address karmaSource_; // Karma source provider address
        uint256 initialSupply_;
        uint256 nextSupply_;
        uint256 initialKarma_;
        uint256 roundsBeforeExpiration_; // how many rounds are passed before claiming is possible
        uint256 supplyDecayPercent_; // defines percentage of next rounds' supply from the current
        address[] sharedOwners_;
        uint256[] sharedOwnersPercs_;
    }

    // END OF VARS
    // ------------------------------------------------------------------------------------

    modifier onlyKarmaSource() {
        if (_msgSender() != karmaSource && (prevKarmaSource == address(0) || _msgSender() != prevKarmaSource))
            revert Distributions__CanOnlyBeCalledByKarmaSource();
        _;
    }

    function initialize(Params calldata params_) public initializer {
        if (params_.initialSupply_ > MAX_ROUND_SUPPLY)
            revert Distributions__InitialSupplyMoreThanMaxRoundSupply();
        if (params_.initialKarma_ == 0)
            revert Distributions__InitialKarmaShouldBeMoreThanZero();
        if (params_.nextSupply_ == 0 || params_.nextSupply_ > MAX_ROUND_SUPPLY)
            revert Distributions__NextSupplyShouldBeBetweenZeroAndMaxRoundSupply();
        if (params_.karmaSource_ == address(0))
            revert Distributions__KarmaSourceShouldNotBeZeroAddress();
        if (params_.owner_ == address(0))
            revert Distributions__OwnerShouldNotBeZeroAddress();
        if (params_.sharedOwners_.length != params_.sharedOwnersPercs_.length)
            revert Distributions__SharedOwnersArrayMustBeSameLengthAsPercentages();
        if (params_.subredditPoints_ == address(0))
            revert Distributions__SubredditPointsShouldNotBeZeroAddress();

        OwnableUpgradeable.__Ownable_init();
        if (params_.owner_ != _msgSender()) {
            OwnableUpgradeable.transferOwnership(params_.owner_);
        }
        _updateSupplyDecayPercent(params_.supplyDecayPercent_);
        _updateRoundsBeforeExpiration(params_.roundsBeforeExpiration_);

        subredditPoints = ISubredditPoints(params_.subredditPoints_);
        subreddit = subredditPoints.subreddit();
        karmaSource = params_.karmaSource_;
        prevKarmaSource = params_.karmaSource_;
        _subredditLowerCase = _toLower(subreddit);

        initialSupply = params_.initialSupply_;
        nextSupply = params_.nextSupply_;

        for (uint i = 0; i < params_.sharedOwners_.length; i++) {
            _updateSharedOwner(params_.sharedOwners_[i], params_.sharedOwnersPercs_[i]);
        }

        if (initialSupply > 0) {
            uint256 sharedOwnersPoints = calcSharedOwnersAvailablePoints(initialSupply);
            _distributionRounds[0] = DistributionRound({
            availablePoints : initialSupply,
            sharedOwnersAvailablePoints : sharedOwnersPoints,
            totalKarma : params_.initialKarma_
            });

            emit AdvanceRound(0, initialSupply, sharedOwnersPoints);
        }
    }

    function claim(uint256 round, address account, uint256 karma, bytes calldata signature) virtual external {
        address signedBy = verifySignature(account, round, karma, signature);
        if (signedBy != karmaSource && (prevKarmaSource == address(0) || signedBy != prevKarmaSource))
            revert Distributions__ClaimIsNotSignedByKarmaSource();
        _mintRound(round, account, karma);
    }

    /* airdrop input format:

        [ ROUND | BATCH 0 … BATCH X ]
        Round is a compressed number, and the batch is:

        [ BATCH_HEADER | BATCH BODY ]
        Where BATCH_HEADER = N * 4 + BATCH_TYPE encoded as a compressed number, which is an equivalent of bit mask:
            +------------+------------+
            |    X-2     |    1-0     |
            +------------+------------+
            | BATCH_SIZE | BATCH_TYPE |
            +------------+------------+

        N is the amount of airdrop records packed within a body and
        BATCH_TYPE is a number 0-3 and defines a batch body:
            AirdropBatchType.ACCOUNTS (0) = [ ACCOUNT_ID 0 | KARMA 0 ... ACCOUNT_ID N-1 | KARMA N-1 ]
            AirdropBatchType.ADDRESSES (1) = [ ADDRESS 0 | KARMA 0 ... ADDRESS N-1 | KARMA N-1 ]
            AirdropBatchType.GROUPED_ACCOUNTS (2) = [ KARMA | ACCOUNT_ID 0 ... ACCOUNT_ID N-1 ]
            AirdropBatchType.GROUPED_ADDRESSES (3) = [ KARMA | ADDRESS 0 ... ADDRESS N-1 ]

        ACCOUNT_ID, KARMA are compressed numbers and ADDRESS is full 20 bytes address.
        Types 2 and 3 are accounts grouped by KARMA, and within a group they all have the same
        amount of karma within a round.
    */

    function airdrop(bytes calldata input) virtual onlyKarmaSource external {
        uint256 ptr = 0;
        uint256 round;
        (round, ptr) = input.decompressUint256(ptr);

        if (ptr >= input.length)
            revert Distributions__AirdropIsEmpty();

        while (ptr < input.length) {
            uint256 batchHeader;
            (batchHeader, ptr) = input.decompressUint256(ptr);
            AirdropBatchType batchType = AirdropBatchType(uint8(batchHeader) & 0x3);
            uint256 batchSize = batchHeader >> 2;
            if (batchSize == 0)
                revert Distributions__AirdropBatchIsEmpty();

            if (batchType == AirdropBatchType.ACCOUNTS) {
                ptr = airdropToAccounts(round, ptr, batchSize, input);
            } else if (batchType == AirdropBatchType.ADDRESSES) {
                ptr = airdropToAddresses(round, ptr, batchSize, input);
            } else if (batchType == AirdropBatchType.GROUPED_ACCOUNTS) {
                ptr = airdropToGroupedAccounts(round, ptr, batchSize, input);
            } else if (batchType == AirdropBatchType.GROUPED_ADDRESSES) {
                ptr = airdropToGroupedAddresses(round, ptr, batchSize, input);
            } else {
                revert Distributions__UnknownAirdropBatchType();
                // shouldn't happen
            }
        }
    }

    // format is [ ACCOUNT_ID 0 | KARMA 0 ... ACCOUNT_ID N-1 | KARMA N-1 ]
    function airdropToAccounts(uint256 round, uint256 ptr, uint256 batchSize, bytes calldata input)
    internal returns (uint256) {
        for (uint256 i = 0; i < batchSize; i++) {
            uint256 accountId;
            (accountId, ptr) = input.decompressUint256(ptr);
            address account = addressOf(accountId);
            if (account == address(0))
                revert Distributions__AccountIdNotFound();

            uint256 karma;
            (karma, ptr) = input.decompressUint256(ptr);

            _mintRound(round, account, karma);
        }
        return ptr;
    }

    // format is [ ADDRESS 0 | KARMA 0 ... ADDRESS N-1 | KARMA N-1 ]
    function airdropToAddresses(uint256 round, uint256 ptr, uint256 batchSize, bytes calldata input)
    internal returns (uint256)  {
        for (uint256 i = 0; i < batchSize; i++) {
            address account = input.toAddress(ptr);
            if (account == address(0))
                revert Distributions__AccountIdNotFound();
            ptr += 20;
            // size of address

            uint256 karma;
            (karma, ptr) = input.decompressUint256(ptr);

            _mintRound(round, account, karma);
        }
        return ptr;
    }

    // format is [ KARMA | ACCOUNT_ID 0 ... ACCOUNT_ID N-1 ]
    function airdropToGroupedAccounts(uint256 round, uint256 ptr, uint256 batchSize, bytes calldata input)
    internal returns (uint256)  {
        uint256 karma;
        (karma, ptr) = input.decompressUint256(ptr);

        for (uint256 i = 0; i < batchSize; i++) {
            uint256 accountId;
            (accountId, ptr) = input.decompressUint256(ptr);
            address account = addressOf(accountId);
            if (account == address(0))
                revert Distributions__AccountIdNotFound();
            _mintRound(round, account, karma);
        }
        return ptr;
    }

    // format is [ KARMA | ADDRESS 0 ... ADDRESS N-1 ]
    function airdropToGroupedAddresses(uint256 round, uint256 ptr, uint256 batchSize, bytes calldata input)
    internal returns (uint256)  {
        uint256 karma;
        (karma, ptr) = input.decompressUint256(ptr);

        for (uint256 i = 0; i < batchSize; i++) {
            address account = input.toAddress(ptr);
            if (account == address(0))
                revert Distributions__AccountIdNotFound();
            ptr += 20;
            // size of address
            _mintRound(round, account, karma);
        }
        return ptr;
    }

    function _mintRound(uint256 round, address account, uint256 karma) internal {
        if (account == address(0))
            revert Distributions__AddressShouldNotBeZero();
        if (karma == 0)
            revert Distributions__KarmaShouldNotBeZero();
        if (_claimableRounds[account] > round)
            revert Distributions__ThisRoundPointsAreAlreadyClaimed();
        if (round > lastRound)
            revert Distributions__TooEarlyToClaimThisRound();
        uint256 earliest = claimableRoundOf(account);
        if (round < earliest)
            revert Distributions__TooLateToClaimThisRound();
        if (!(earliest == 0 || round == earliest || _msgSender() == account || _msgSender() == karmaSource))
            revert Distributions__OnlyOwnerCanClaimRoundThatIsNotEarliest();

        DistributionRound memory dr = _distributionRounds[round];
        if (dr.availablePoints == 0)
            revert Distributions__NoPointsToClaimThisRound();
        if (dr.totalKarma == 0)
            revert Distributions__NoKarmaInThisRound();
        uint256 userPoints = (dr.availablePoints - dr.sharedOwnersAvailablePoints) * karma /dr.totalKarma;
        if (userPoints == 0)
            revert Distributions__UserKarmaTooLowToClaimPoints();
        _prevClaimed = _prevClaimed + userPoints;
        _claimableRounds[account] = round  + 1;
        _registerAccount(account);
        emit ClaimPoints(round, account, karma, userPoints);
        subredditPoints.mint(address(this), account, userPoints, "", "");
    }

    // corresponding _distributionRounds mappings are added with
    //  + every next distribution supply is `previous - decay` and stored in nextSupply
    //  + distributed 50% of burned points in a previous round
    // rounds are removed if they are not claimable anymore
    function advanceToRound(uint256 round, uint256 totalKarma) virtual onlyKarmaSource external {
        if (round != lastRound + 1)
            revert Distributions__RoundShouldBeIncrementallyIncreasing();
        if (totalKarma == 0)
            revert Distributions__TotalKarmaShouldBeGreaterThanZero();
        uint256 mc = minClaimableRound();

        if (mc > 0) {
            // delete non claimable round data
            delete (_distributionRounds[mc-1]);
        }

        uint256 ts = IERC20Upgradeable(address(subredditPoints)).totalSupply();
        uint256 prevClaimedCopy = _prevClaimed;

        // get round points
        uint256 roundPoints = nextSupply;
        // reintroduce 50 % of previously burned tokens
        uint256 ps = _prevRoundSupply+_prevClaimed+_prevOwnersShare;
        if (ps > ts) {
            roundPoints = roundPoints+((ps-ts)/2);
        }

        // decay next supply
        if (nextSupply > 0 && supplyDecayPercent > 0) {
            nextSupply = nextSupply - ((nextSupply * supplyDecayPercent) / PERCENT_PRECISION);
        }

        // set distribution round data for this round
        uint256 sharedOwnersPoints = 0;
        if (roundPoints > 0) {
            sharedOwnersPoints = calcSharedOwnersAvailablePoints(roundPoints);
            _distributionRounds[round] = DistributionRound({
            availablePoints : roundPoints,
            sharedOwnersAvailablePoints : sharedOwnersPoints,
            totalKarma : totalKarma
            });
        }

        emit AdvanceRound(round, roundPoints, sharedOwnersPoints);

        lastRound = round;
        _prevRoundSupply = ts;
        _prevClaimed = 0;
        _prevOwnersShare = 0;

        // distribute shared cut, but no more than it was claimed by users
        // this protects from exceeding total amount by increasing percentage between rounds
        if (sharedOwnersPoints > 0 && prevClaimedCopy > 0) {
            uint256 totalSharedPercent;
            for (uint256 i = 0; i < sharedOwners.length; i++) {
                totalSharedPercent = totalSharedPercent + sharedOwners[i].percent;
            }

            uint256 claimedPlusShared =
                (prevClaimedCopy * PERCENT_PRECISION) / (PERCENT_PRECISION - totalSharedPercent);
            uint256 sharedLeft = claimedPlusShared - prevClaimedCopy;

            for (uint256 i = 0; i < sharedOwners.length && sharedLeft > 0; i++) {
                uint256 ownerPoints = (claimedPlusShared * sharedOwners[i].percent) / PERCENT_PRECISION;
                if (ownerPoints > 0 && ownerPoints <= sharedLeft) {
                    // keep track of owners points minted for this round
                    _prevOwnersShare = _prevOwnersShare + ownerPoints;
                    subredditPoints.mint(address(this), sharedOwners[i].account, ownerPoints, "", "");
                    sharedLeft = sharedLeft - ownerPoints;
                }
            }
        }
    }

    function totalSharedOwners() external view returns (uint256) {
        return sharedOwners.length;
    }

    function updateSupplyDecayPercent(uint256 _supplyDecayPercent) external onlyOwner {
        _updateSupplyDecayPercent(_supplyDecayPercent);
    }

    function _updateSupplyDecayPercent(uint256 _supplyDecayPercent) internal {
        if (_supplyDecayPercent >= PERCENT_PRECISION)
            revert Distributions__PercentShouldBeLessThanPERCENT_PRECISION();
        supplyDecayPercent = _supplyDecayPercent;
        emit SupplyDecayPercentUpdated(_supplyDecayPercent);
    }

    function updateRoundsBeforeExpiration(uint256 _roundsBeforeExpiration) external onlyOwner {
        _updateRoundsBeforeExpiration(_roundsBeforeExpiration);
    }

    function _updateRoundsBeforeExpiration(uint256 _roundsBeforeExpiration) internal {
        roundsBeforeExpiration = _roundsBeforeExpiration;
        emit RoundsBeforeExpirationUpdated(_roundsBeforeExpiration);
    }

    function minClaimableRound() public view returns (uint256) {
        if (lastRound >= roundsBeforeExpiration) {
            return lastRound - roundsBeforeExpiration;
        }
        return 0;
    }

    function verifySignature(address account, uint256 round, uint256 karma, bytes memory signature)
    internal view returns (address) {

        bytes32 hash = keccak256(abi.encode(_subredditLowerCase, uint256(round), account, karma));
        bytes32 prefixedHash = ECDSAUpgradeable.toEthSignedMessageHash(hash);
        return ECDSAUpgradeable.recover(prefixedHash, signature);
    }

    function calcSharedOwnersAvailablePoints(uint256 points) internal view returns (uint256) {
        uint256 r;
        for (uint256 i = 0; i < sharedOwners.length; i++) {
            r += calcSharedPoints(points, sharedOwners[i]);
        }
        return r;
    }

    function calcSharedPoints(uint256 points, SharedOwner memory sharedOwner) internal pure returns (uint256) {
        return points * sharedOwner.percent / PERCENT_PRECISION;
    }

    function updateKarmaSource(address _karmaSource) external onlyOwner {
        if (_karmaSource == address(0))
            revert Distributions__KarmaSourceShouldNotBeZeroAddress();
        prevKarmaSource = karmaSource;
        karmaSource = _karmaSource;
        emit KarmaSourceUpdated(_karmaSource, prevKarmaSource);
    }

    // shared owners get their points 1 round later within advancement
    // increasing total shared percentage can lead to some of the owners not receiving their cut within a next round
    function updateSharedOwner(address account, uint256 percent) external onlyOwner {
        _updateSharedOwner(account, percent);
    }

    function _updateSharedOwner(address account, uint256 percent) internal {
        if (percent >= PERCENT_PRECISION)
            revert Distributions__PercentShouldBeLessThanPERCENT_PRECISION();
        if (percent != 0 && sharedOwners.length >= MAX_SHARED_OWNERS )
            revert Distributions__SharedOwnersLimitReached(sharedOwners.length, MAX_SHARED_OWNERS);

        bool updated = false;

        for (uint256 i = 0; i < sharedOwners.length; i++) {
            SharedOwner memory so = sharedOwners[i];
            if (so.account == account) {
                if (percent == 0) {
                    if (i != (sharedOwners.length - 1)) {// if it's not last element, replace it from the tail
                        sharedOwners[i] = sharedOwners[sharedOwners.length - 1];
                    }
                    // remove tail
                    sharedOwners.pop();
                } else {
                    sharedOwners[i].percent = percent;
                }
                updated = true;
            }
        }

        if (!updated) {
            if (percent == 0) {
                return;
            }
            sharedOwners.push(SharedOwner(account, percent));
        }

        checkSharedPercentage();
        // allow to update sharedOwnersAvailablePoints for a rounds which aren't claimed yet
        DistributionRound storage dr = _distributionRounds[lastRound];
        if (_prevClaimed == 0 && dr.availablePoints > 0) {
            dr.sharedOwnersAvailablePoints = calcSharedOwnersAvailablePoints(dr.availablePoints);
        }
        emit SharedOwnerUpdated(_msgSender(), account, percent);
    }

    function checkSharedPercentage() internal view {
        uint256 total;
        for (uint256 i = 0; i < sharedOwners.length; i++) {
            total += sharedOwners[i].percent;
        }
        if (total >= PERCENT_PRECISION)
            revert Distributions__CantShareAll100PercentOfPoints(total);
    }

    function percentPrecision() external pure returns (uint256) {
        return PERCENT_PRECISION;
    }

    function _toLower(string memory str) internal pure returns (string memory) {
        bytes memory bStr = bytes(str);
        bytes memory bLower = new bytes(bStr.length);
        for (uint i = 0; i < bStr.length; i++) {
            if ((int8(uint8(bStr[i])) >= 65) && (int8(uint8(bStr[i])) <= 90)) {
                bLower[i] = bytes1(uint8(bStr[i]) + 32);
            } else {
                bLower[i] = bStr[i];
            }
        }
        return string(bLower);
    }

    function claimableRoundOf(address account) public view returns (uint256) {
        uint256 mc = minClaimableRound();
        if (mc > _claimableRounds[account]) {
            return mc;
        }
        return _claimableRounds[account];
    }

    function addressOf(uint256 accountId) public view returns (address) {
        return _accountAddresses[accountId];
    }

    function accountIdOf(address account) public view returns (uint256) {
        return _accountIds[account];
    }

    function _registerAccount(address account) internal {
        if (_accountIds[account] == 0) {
            _lastAccountId+=1;
            _accountIds[account] = _lastAccountId;
            _accountAddresses[_lastAccountId] = account;
            emit AccountRegistered(account, _lastAccountId);
        }
    }
}
        

@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal onlyInitializing {
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}
          

Contract ABI

[{"type":"error","name":"Distributions__AccountIdNotFound","inputs":[]},{"type":"error","name":"Distributions__AddressShouldNotBeZero","inputs":[]},{"type":"error","name":"Distributions__AirdropBatchIsEmpty","inputs":[]},{"type":"error","name":"Distributions__AirdropIsEmpty","inputs":[]},{"type":"error","name":"Distributions__CanOnlyBeCalledByKarmaSource","inputs":[]},{"type":"error","name":"Distributions__CantShareAll100PercentOfPoints","inputs":[{"type":"uint256","name":"total","internalType":"uint256"}]},{"type":"error","name":"Distributions__ClaimIsNotSignedByKarmaSource","inputs":[]},{"type":"error","name":"Distributions__InitialKarmaShouldBeMoreThanZero","inputs":[]},{"type":"error","name":"Distributions__InitialSupplyMoreThanMaxRoundSupply","inputs":[]},{"type":"error","name":"Distributions__KarmaShouldNotBeZero","inputs":[]},{"type":"error","name":"Distributions__KarmaSourceShouldNotBeZeroAddress","inputs":[]},{"type":"error","name":"Distributions__NextSupplyShouldBeBetweenZeroAndMaxRoundSupply","inputs":[]},{"type":"error","name":"Distributions__NoKarmaInThisRound","inputs":[]},{"type":"error","name":"Distributions__NoPointsToClaimThisRound","inputs":[]},{"type":"error","name":"Distributions__OnlyOwnerCanClaimRoundThatIsNotEarliest","inputs":[]},{"type":"error","name":"Distributions__OwnerShouldNotBeZeroAddress","inputs":[]},{"type":"error","name":"Distributions__PercentShouldBeLessThanPERCENT_PRECISION","inputs":[]},{"type":"error","name":"Distributions__RoundShouldBeIncrementallyIncreasing","inputs":[]},{"type":"error","name":"Distributions__SharedOwnersArrayMustBeSameLengthAsPercentages","inputs":[]},{"type":"error","name":"Distributions__SharedOwnersLimitReached","inputs":[{"type":"uint256","name":"numSharedOwners","internalType":"uint256"},{"type":"uint256","name":"MAX_SHARED_OWNERS","internalType":"uint256"}]},{"type":"error","name":"Distributions__SubredditPointsShouldNotBeZeroAddress","inputs":[]},{"type":"error","name":"Distributions__ThisRoundPointsAreAlreadyClaimed","inputs":[]},{"type":"error","name":"Distributions__TooEarlyToClaimThisRound","inputs":[]},{"type":"error","name":"Distributions__TooLateToClaimThisRound","inputs":[]},{"type":"error","name":"Distributions__TotalKarmaShouldBeGreaterThanZero","inputs":[]},{"type":"error","name":"Distributions__UnknownAirdropBatchType","inputs":[]},{"type":"error","name":"Distributions__UserKarmaTooLowToClaimPoints","inputs":[]},{"type":"error","name":"decompressUint256__OutOfBounds","inputs":[]},{"type":"error","name":"decompressUint256__Overflow","inputs":[]},{"type":"event","name":"AccountRegistered","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"accountId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"AdvanceRound","inputs":[{"type":"uint256","name":"round","internalType":"uint256","indexed":false},{"type":"uint256","name":"totalPoints","internalType":"uint256","indexed":false},{"type":"uint256","name":"sharedOwnersPoints","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"ClaimPoints","inputs":[{"type":"uint256","name":"round","internalType":"uint256","indexed":false},{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"karma","internalType":"uint256","indexed":false},{"type":"uint256","name":"points","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"KarmaSourceUpdated","inputs":[{"type":"address","name":"karmaSource","internalType":"address","indexed":false},{"type":"address","name":"prevKarmaSource","internalType":"address","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":"RoundsBeforeExpirationUpdated","inputs":[{"type":"uint256","name":"roundsBeforeExpiration","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SharedOwnerUpdated","inputs":[{"type":"address","name":"_from","internalType":"address","indexed":true},{"type":"address","name":"_to","internalType":"address","indexed":true},{"type":"uint256","name":"_percent","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SupplyDecayPercentUpdated","inputs":[{"type":"uint256","name":"supplyDecayPercent","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MAX_ROUND_SUPPLY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MAX_SHARED_OWNERS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"PERCENT_PRECISION","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"accountIdOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"addressOf","inputs":[{"type":"uint256","name":"accountId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"advanceToRound","inputs":[{"type":"uint256","name":"round","internalType":"uint256"},{"type":"uint256","name":"totalKarma","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"airdrop","inputs":[{"type":"bytes","name":"input","internalType":"bytes"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"claim","inputs":[{"type":"uint256","name":"round","internalType":"uint256"},{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"karma","internalType":"uint256"},{"type":"bytes","name":"signature","internalType":"bytes"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"claimableRoundOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"initialSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initialize","inputs":[{"type":"tuple","name":"params_","internalType":"struct Distributions.Params","components":[{"type":"address","name":"owner_","internalType":"address"},{"type":"address","name":"subredditPoints_","internalType":"address"},{"type":"address","name":"karmaSource_","internalType":"address"},{"type":"uint256","name":"initialSupply_","internalType":"uint256"},{"type":"uint256","name":"nextSupply_","internalType":"uint256"},{"type":"uint256","name":"initialKarma_","internalType":"uint256"},{"type":"uint256","name":"roundsBeforeExpiration_","internalType":"uint256"},{"type":"uint256","name":"supplyDecayPercent_","internalType":"uint256"},{"type":"address[]","name":"sharedOwners_","internalType":"address[]"},{"type":"uint256[]","name":"sharedOwnersPercs_","internalType":"uint256[]"}]}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"karmaSource","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"lastRound","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"minClaimableRound","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"nextSupply","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"pure","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"percentPrecision","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"prevKarmaSource","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"roundsBeforeExpiration","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"percent","internalType":"uint256"}],"name":"sharedOwners","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"subreddit","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract ISubredditPoints"}],"name":"subredditPoints","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"supplyDecayPercent","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSharedOwners","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateKarmaSource","inputs":[{"type":"address","name":"_karmaSource","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateRoundsBeforeExpiration","inputs":[{"type":"uint256","name":"_roundsBeforeExpiration","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateSharedOwner","inputs":[{"type":"address","name":"account","internalType":"address"},{"type":"uint256","name":"percent","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateSupplyDecayPercent","inputs":[{"type":"uint256","name":"_supplyDecayPercent","internalType":"uint256"}]}]
              

Contract Creation Code

0x608060405234801561001057600080fd5b50612fc4806100206000396000f3fe608060405234801561001057600080fd5b50600436106101695760003560e01c806215d5b11461016e57806311a800bc1461019e57806315c4b528146101b1578063197ad63a146101c65780632785bf16146101dc5780632ad9b698146101ef5780632fd4af05146101f8578063303238a21461020b578063378dc3dc1461021357806351ad5a971461021c57806356df8adb146102255780635a5f219414610238578063636049a71461024b578063715018a61461025e5780637e6b26cf146102665780637f3619281461027957806382bc07e61461028c57806386f7a3941461029557806389304061146102a85780638da5cb5b146102bb57806399016142146102c3578063ad93b0ab146102d6578063b361b00f146102ff578063b994a7c514610312578063bdc330cb1461031b578063c32be42814610330578063e361349d14610362578063f2fde38b1461036a578063f9714c0c1461037d578063ffe8e37214610386575b600080fd5b606654610181906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101816101ac36600461295a565b610390565b6101c46101bf366004612973565b6103ab565b005b6101ce61095c565b604051908152602001610195565b6101c46101ea3660046129ef565b610984565b6101ce606e5481565b6101c4610206366004612a4c565b610bae565b6101ce60c881565b6101ce606c5481565b6101ce606f5481565b6101c4610233366004612a76565b610be7565b607354610181906001600160a01b031681565b6101c461025936600461295a565b611060565b6101c461109b565b6101c461027436600461295a565b6110d6565b6101c4610287366004612a98565b61110e565b6101ce60695481565b6101ce6102a3366004612a98565b6111d1565b606554610181906001600160a01b031681565b610181611222565b6101c46102d1366004612ab3565b611231565b6101ce6102e4366004612a98565b6001600160a01b031660009081526075602052604090205490565b6101ce680a18f07d736b90be55601d1b81565b620f42406101ce565b6103236112e8565b6040516101959190612b46565b61034361033e36600461295a565b611376565b604080516001600160a01b039093168352602083019190915201610195565b6070546101ce565b6101c4610378366004612a98565b6113ae565b6101ce606d5481565b6101ce620f424081565b6000908152607460205260409020546001600160a01b031690565b600054610100900460ff166103c65760005460ff16156103ca565b303b155b6104325760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b600054610100900460ff16158015610454576000805461ffff19166101011790555b680a18f07d736b90be55601d1b8260600135111561048557604051633f23f64d60e21b815260040160405180910390fd5b60a08201356104a75760405163e32538b760e01b815260040160405180910390fd5b608082013515806104c75750680a18f07d736b90be55601d1b8260800135115b156104e557604051630e9165d160e01b815260040160405180910390fd5b60006104f76060840160408501612a98565b6001600160a01b0316141561051f5760405163ae2b6bdd60e01b815260040160405180910390fd5b600061052e6020840184612a98565b6001600160a01b0316141561055657604051635cd2fa1560e01b815260040160405180910390fd5b610564610120830183612b79565b9050610574610100840184612b79565b9050146105945760405163d88d538b60e01b815260040160405180910390fd5b60006105a66040840160208501612a98565b6001600160a01b031614156105ce5760405163592ca6df60e11b815260040160405180910390fd5b6105d661144b565b336105e46020840184612a98565b6001600160a01b031614610602576106026103786020840184612a98565b61060f8260e0013561147a565b61061c8260c001356114d2565b61062c6040830160208401612a98565b606580546001600160a01b0319166001600160a01b039290921691821790556040805163bdc330cb60e01b8152905163bdc330cb916004808201926000929091908290030181865afa158015610686573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106ae9190810190612bd8565b80516106c2916067916020909101906128c1565b506106d36060830160408401612a98565b606680546001600160a01b0319166001600160a01b03929092169190911790556107036060830160408401612a98565b607360006101000a8154816001600160a01b0302191690836001600160a01b031602179055506107bc6067805461073990612c79565b80601f016020809104026020016040519081016040528092919081815260200182805461076590612c79565b80156107b25780601f10610787576101008083540402835291602001916107b2565b820191906000526020600020905b81548152906001019060200180831161079557829003601f168201915b5050505050611507565b80516107d0916068916020909101906128c1565b506060820135606c556080820135606e5560005b6107f2610100840184612b79565b905081101561086d5761085b61080c610100850185612b79565b8381811061081c5761081c612cb4565b90506020020160208101906108319190612a98565b61083f610120860186612b79565b8481811061084f5761084f612cb4565b90506020020135611669565b8061086581612ce0565b9150506107e4565b50606c5415610946576000610883606c5461196f565b60408051606081018252606c54808252602080830185815260a08901358486019081526000808052606a90935293517f6021fa82de881996a3e5fd2d032f74dfe72746b8a66c5510d4ab1a3cb789150755517f6021fa82de881996a3e5fd2d032f74dfe72746b8a66c5510d4ab1a3cb78915085591517f6021fa82de881996a3e5fd2d032f74dfe72746b8a66c5510d4ab1a3cb7891509559151929350600080516020612f6f8339815191529261093c92908590612cfb565b60405180910390a1505b8015610958576000805461ff00191690555b5050565b6000606d546069541061097e57606d546069546109799190612d11565b905090565b50600090565b6066546001600160a01b0316336001600160a01b0316141580156109cf57506073546001600160a01b031615806109cf57506073546001600160a01b0316336001600160a01b031614155b156109ed5760405163b1b4decb60e01b815260040160405180910390fd5b600080610a338285858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b92509050828210610a5757604051638397fe2960e01b815260040160405180910390fd5b82821015610ba8576000610aa48386868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b935090506000600380831690811115610abf57610abf612d28565b9050600282901c80610ae457604051632709375d60e21b815260040160405180910390fd5b6000826003811115610af857610af8612d28565b1415610b1257610b0b8486838a8a611b3f565b9450610ba0565b6001826003811115610b2657610b26612d28565b1415610b3957610b0b8486838a8a611c3d565b6002826003811115610b4d57610b4d612d28565b1415610b6057610b0b8486838a8a611d2d565b6003826003811115610b7457610b74612d28565b1415610b8757610b0b8486838a8a611e2b565b6040516325cb653b60e21b815260040160405180910390fd5b505050610a57565b50505050565b33610bb7611222565b6001600160a01b031614610bdd5760405162461bcd60e51b815260040161042990612d3e565b6109588282611669565b6066546001600160a01b0316336001600160a01b031614158015610c3257506073546001600160a01b03161580610c3257506073546001600160a01b0316336001600160a01b031614155b15610c505760405163b1b4decb60e01b815260040160405180910390fd5b606954610c5e906001612d73565b8214610c7c57604051622cbe4b60e91b815260040160405180910390fd5b80610c9a57604051630bff745560e11b815260040160405180910390fd5b6000610ca461095c565b90508015610cdc57606a6000610cbb600184612d11565b81526020810191909152604001600090812081815560018101829055600201555b606554604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd9160048083019260209291908290030181865afa158015610d26573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d4a9190612d8b565b607254606e5460775460715493945091929091600091610d6b908590612d73565b610d759190612d73565b905083811115610da2576002610d8b8583612d11565b610d959190612da4565b610d9f9083612d73565b91505b6000606e54118015610db657506000606f54115b15610dea57620f4240606f54606e54610dcf9190612dc6565b610dd99190612da4565b606e54610de69190612d11565b606e555b60008215610e3957610dfb8361196f565b6040805160608101825285815260208082018481528284018c815260008e8152606a9093529390912091518255516001820155905160029091015590505b600080516020612f6f833981519152888483604051610e5a93929190612cfb565b60405180910390a160698890556071859055600060728190556077558015801590610e855750600084115b15611056576000805b607054811015610edc5760708181548110610eab57610eab612cb4565b90600052602060002090600202016001015482610ec89190612d73565b915080610ed481612ce0565b915050610e8e565b506000610eec82620f4240612d11565b610ef9620f424088612dc6565b610f039190612da4565b90506000610f118783612d11565b905060005b60705481108015610f275750600082115b15611051576000620f424060708381548110610f4557610f45612cb4565b90600052602060002090600202016001015485610f629190612dc6565b610f6c9190612da4565b9050600081118015610f7e5750828111155b1561103e5780607754610f919190612d73565b607755606554607080546001600160a01b039092169163ab89013b91309186908110610fbf57610fbf612cb4565b60009182526020909120600290910201546040516001600160e01b031960e085901b168152610ffd92916001600160a01b0316908690600401612de5565b600060405180830381600087803b15801561101757600080fd5b505af115801561102b573d6000803e3d6000fd5b50505050808361103b9190612d11565b92505b508061104981612ce0565b915050610f16565b505050505b5050505050505050565b33611069611222565b6001600160a01b03161461108f5760405162461bcd60e51b815260040161042990612d3e565b611098816114d2565b50565b336110a4611222565b6001600160a01b0316146110ca5760405162461bcd60e51b815260040161042990612d3e565b6110d46000611f19565b565b336110df611222565b6001600160a01b0316146111055760405162461bcd60e51b815260040161042990612d3e565b6110988161147a565b33611117611222565b6001600160a01b03161461113d5760405162461bcd60e51b815260040161042990612d3e565b6001600160a01b0381166111645760405163ae2b6bdd60e01b815260040160405180910390fd5b60668054607380546001600160a01b038084166001600160a01b0319928316811790935592169184169182179092556040805191825260208201929092527fea601ecdff79475b71fb4d1545a7ebca8349b2af1ca1ff06dae26d8598067e1291015b60405180910390a150565b6000806111dc61095c565b6001600160a01b0384166000908152606b60205260409020549091508111156112055792915050565b50506001600160a01b03166000908152606b602052604090205490565b6033546001600160a01b031690565b600061127585878686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611f6b92505050565b6066549091506001600160a01b038083169116148015906112b757506073546001600160a01b031615806112b757506073546001600160a01b03828116911614155b156112d557604051636db7f57960e11b815260040160405180910390fd5b6112e086868661200d565b505050505050565b606780546112f590612c79565b80601f016020809104026020016040519081016040528092919081815260200182805461132190612c79565b801561136e5780601f106113435761010080835404028352916020019161136e565b820191906000526020600020905b81548152906001019060200180831161135157829003601f168201915b505050505081565b6070818154811061138657600080fd5b6000918252602090912060029091020180546001909101546001600160a01b03909116915082565b336113b7611222565b6001600160a01b0316146113dd5760405162461bcd60e51b815260040161042990612d3e565b6001600160a01b0381166114425760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610429565b61109881611f19565b600054610100900460ff166114725760405162461bcd60e51b815260040161042990612e26565b6110d46122ee565b620f4240811061149d576040516356f1e3d760e11b815260040160405180910390fd5b606f8190556040518181527f3fc8c6c6a50072e9b4dcce50dc8a4f21877704651dff1c72f102afde8e08016b906020016111c6565b606d8190556040518181527f9b9d9ee399388905981700b7c8de8ca8853dbf397b77d509d7ed7a3d4dff52a6906020016111c6565b60606000829050600081516001600160401b0381111561152957611529612bc2565b6040519080825280601f01601f191660200182016040528015611553576020820181803683370190505b50905060005b825181101561166157604183828151811061157657611576612cb4565b016020015160f81d128015906115a65750605a83828151811061159b5761159b612cb4565b016020015160f81d13155b15611608578281815181106115bd576115bd612cb4565b602001015160f81c60f81b60f81c60206115d79190612e71565b60f81b8282815181106115ec576115ec612cb4565b60200101906001600160f81b031916908160001a90535061164f565b82818151811061161a5761161a612cb4565b602001015160f81c60f81b82828151811061163757611637612cb4565b60200101906001600160f81b031916908160001a9053505b8061165981612ce0565b915050611559565b509392505050565b620f4240811061168c576040516356f1e3d760e11b815260040160405180910390fd5b801580159061169e575060705460c811155b156116ca5760705460405163dbeb32b760e01b8152600481019190915260c86024820152604401610429565b6000805b607054811015611846576000607082815481106116ed576116ed612cb4565b60009182526020918290206040805180820190915260029092020180546001600160a01b0390811680845260019092015493830193909352909250908616141561183357836118045760705461174590600190612d11565b82146117c5576070805461175b90600190612d11565b8154811061176b5761176b612cb4565b90600052602060002090600202016070838154811061178c5761178c612cb4565b60009182526020909120825460029092020180546001600160a01b0319166001600160a01b039092169190911781556001918201549101555b60708054806117d6576117d6612e96565b60008281526020812060026000199093019283020180546001600160a01b031916815560010155905561182e565b836070838154811061181857611818612cb4565b9060005260206000209060020201600101819055505b600192505b508061183e81612ce0565b9150506116ce565b50806118ea578161185657505050565b604080518082019091526001600160a01b038481168252602082018481526070805460018101825560009190915292517f8f6b23ffa15f0465e3176e15ca644cf24f86dc1312fe715484e3c4aead5eb78b600290940293840180546001600160a01b0319169190931617909155517f8f6b23ffa15f0465e3176e15ca644cf24f86dc1312fe715484e3c4aead5eb78c909101555b6118f261231e565b6069546000908152606a602052604090206072541580156119135750805415155b156119295780546119239061196f565b60018201555b6040518381526001600160a01b0385169033907f67efa3716a83d716fc277019d454659a46cd1a11e7f4518b63ce585d5e6713e59060200160405180910390a350505050565b60008060005b6070548110156119ec576119ce846070838154811061199657611996612cb4565b60009182526020918290206040805180820190915260029092020180546001600160a01b031682526001015491810191909152612396565b6119d89083612d73565b9150806119e481612ce0565b915050611975565b5092915050565b600080835160001415611a195760405163517aa30d60e01b815260040160405180910390fd5b6000611a2585856123bd565b905060808116611a69576000611a3c856001612d73565b9050611a49868683612419565b607f8216611a58866001612d73565b8160ff169150935093505050611b38565b608060c082161415611add576000611a82856002612d73565b9050611a8f868683612419565b6000611aa6611a9f876001612d73565b88906123bd565b60ff16905080611abb603f8516610100612dc6565b611ac59190612d73565b611ad0906080612d73565b8294509450505050611b38565b6000611af0600183811c601f1690612e71565b60ff1690506000611b018287612d73565b611b0c906001612d73565b9050611b19878783612419565b60018683018801810151600890930281011b6000190190911693509150505b9250929050565b6000805b84811015611c32576000611b908786868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b975090506000611b9f82610390565b90506001600160a01b038116611bc857604051638048717f60e01b815260040160405180910390fd5b6000611c0d8988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b99509050611c1c8a838361200d565b5050508080611c2a90612ce0565b915050611b43565b509395945050505050565b6000805b84811015611c32576000611c8e8786868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506124619050565b90506001600160a01b038116611cb757604051638048717f60e01b815260040160405180910390fd5b611cc2601488612d73565b96506000611d098887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b98509050611d1889838361200d565b50508080611d2590612ce0565b915050611c41565b600080611d738685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b9650905060005b85811015611e1f576000611dc78887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b985090506000611dd682610390565b90506001600160a01b038116611dff57604051638048717f60e01b815260040160405180910390fd5b611e0a8a828661200d565b50508080611e1790612ce0565b915050611d7a565b50949695505050505050565b600080611e718685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b9650905060005b85811015611e1f576000611ec58887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506124619050565b90506001600160a01b038116611eee57604051638048717f60e01b815260040160405180910390fd5b611ef9601489612d73565b9750611f0689828561200d565b5080611f1181612ce0565b915050611e78565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000806068858786604051602001611f869493929190612eac565b6040516020818303038152906040528051906020012090506000611ff6826040517b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b905061200281856124c6565b979650505050505050565b6001600160a01b038216612034576040516362ce452360e11b815260040160405180910390fd5b8061205257604051631d4a43d160e31b815260040160405180910390fd5b6001600160a01b0382166000908152606b602052604090205483101561208b57604051636ccebf8760e01b815260040160405180910390fd5b6069548311156120ae57604051630312c8bb60e61b815260040160405180910390fd5b60006120b9836111d1565b9050808410156120dc576040516339cf91f560e01b815260040160405180910390fd5b8015806120e857508084145b806120fb5750336001600160a01b038416145b8061211957506066546001600160a01b0316336001600160a01b0316145b6121365760405163606133af60e11b815260040160405180910390fd5b6000848152606a6020908152604091829020825160608101845281548082526001830154938201939093526002909101549281019290925261218b5760405163e8c06a8b60e01b815260040160405180910390fd5b60408101516121ad576040516356041c3560e01b815260040160405180910390fd5b6000816040015184836020015184600001516121c99190612d11565b6121d39190612dc6565b6121dd9190612da4565b9050806121fd576040516385ffe97960e01b815260040160405180910390fd5b8060725461220b9190612d73565b607255612219866001612d73565b6001600160a01b0386166000908152606b602052604090205561223b856124e2565b846001600160a01b03167f05c0dd48c9c829cad200bd1db15fa75fb03cae88add3bd16cb8899cc39412f7487868460405161227893929190612cfb565b60405180910390a260655460405163ab89013b60e01b81526001600160a01b039091169063ab89013b906122b490309089908690600401612de5565b600060405180830381600087803b1580156122ce57600080fd5b505af11580156122e2573d6000803e3d6000fd5b50505050505050505050565b600054610100900460ff166123155760405162461bcd60e51b815260040161042990612e26565b6110d433611f19565b6000805b607054811015612370576070818154811061233f5761233f612cb4565b9060005260206000209060020201600101548261235c9190612d73565b91508061236881612ce0565b915050612322565b50620f424081106110985760405162202b6360ea1b815260048101829052602401610429565b6000620f42408260200151846123ac9190612dc6565b6123b69190612da4565b9392505050565b60006123ca826001612d73565b835110156124105760405162461bcd60e51b8152602060048201526013602482015272746f55696e74385f6f75744f66426f756e647360681b6044820152606401610429565b50016001015190565b8181101561243a57604051635407ffff60e11b815260040160405180910390fd5b808351101561245c5760405163517aa30d60e01b815260040160405180910390fd5b505050565b600061246e826014612d73565b835110156124b65760405162461bcd60e51b8152602060048201526015602482015274746f416464726573735f6f75744f66426f756e647360581b6044820152606401610429565b500160200151600160601b900490565b60008060006124d58585612582565b91509150611661816125ef565b6001600160a01b038116600090815260756020526040902054611098576001607660008282546125129190612d73565b9091555050607680546001600160a01b03831660008181526075602090815260408083208590559382526074905282812080546001600160a01b0319168317905592549151919290917fefd1ddef00b1051abc144c2e895de70a10dbbc3ad8985118c74c15e40e3d391f9190a350565b6000808251604114156125b95760208301516040840151606085015160001a6125ad878285856127a5565b94509450505050611b38565b8251604014156125e357602083015160408401516125d8868383612888565b935093505050611b38565b50600090506002611b38565b600081600481111561260357612603612d28565b141561260c5750565b600181600481111561262057612620612d28565b14156126695760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b6044820152606401610429565b600281600481111561267d5761267d612d28565b14156126cb5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610429565b60038160048111156126df576126df612d28565b14156127385760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610429565b600481600481111561274c5761274c612d28565b14156110985760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b6064820152608401610429565b6000806fa2a8918ca85bafe22016d0b997e4df60600160ff1b038311156127d2575060009050600361287f565b8460ff16601b141580156127ea57508460ff16601c14155b156127fb575060009050600461287f565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa15801561284f573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166128785760006001925092505061287f565b9150600090505b94509492505050565b6000806001600160ff1b038316816128a560ff86901c601b612d73565b90506128b3878288856127a5565b935093505050935093915050565b8280546128cd90612c79565b90600052602060002090601f0160209004810192826128ef5760008555612935565b82601f1061290857805160ff1916838001178555612935565b82800160010185558215612935579182015b8281111561293557825182559160200191906001019061291a565b50612941929150612945565b5090565b5b808211156129415760008155600101612946565b60006020828403121561296c57600080fd5b5035919050565b60006020828403121561298557600080fd5b81356001600160401b0381111561299b57600080fd5b820161014081850312156123b657600080fd5b60008083601f8401126129c057600080fd5b5081356001600160401b038111156129d757600080fd5b602083019150836020828501011115611b3857600080fd5b60008060208385031215612a0257600080fd5b82356001600160401b03811115612a1857600080fd5b612a24858286016129ae565b90969095509350505050565b80356001600160a01b0381168114612a4757600080fd5b919050565b60008060408385031215612a5f57600080fd5b612a6883612a30565b946020939093013593505050565b60008060408385031215612a8957600080fd5b50508035926020909101359150565b600060208284031215612aaa57600080fd5b6123b682612a30565b600080600080600060808688031215612acb57600080fd5b85359450612adb60208701612a30565b93506040860135925060608601356001600160401b03811115612afd57600080fd5b612b09888289016129ae565b969995985093965092949392505050565b60005b83811015612b35578181015183820152602001612b1d565b83811115610ba85750506000910152565b6020815260008251806020840152612b65816040850160208701612b1a565b601f01601f19169190910160400192915050565b6000808335601e19843603018112612b9057600080fd5b8301803591506001600160401b03821115612baa57600080fd5b6020019150600581901b3603821315611b3857600080fd5b634e487b7160e01b600052604160045260246000fd5b600060208284031215612bea57600080fd5b81516001600160401b0380821115612c0157600080fd5b818401915084601f830112612c1557600080fd5b815181811115612c2757612c27612bc2565b604051601f8201601f19908116603f01168101908382118183101715612c4f57612c4f612bc2565b81604052828152876020848701011115612c6857600080fd5b612002836020830160208801612b1a565b600181811c90821680612c8d57607f821691505b60208210811415612cae57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612cf457612cf4612cca565b5060010190565b9283526020830191909152604082015260600190565b600082821015612d2357612d23612cca565b500390565b634e487b7160e01b600052602160045260246000fd5b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60008219821115612d8657612d86612cca565b500190565b600060208284031215612d9d57600080fd5b5051919050565b600082612dc157634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615612de057612de0612cca565b500290565b6001600160a01b039384168152919092166020820152604081019190915260a060608201819052600090820181905260c06080830181905282015260e00190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600060ff821660ff84168060ff03821115612e8e57612e8e612cca565b019392505050565b634e487b7160e01b600052603160045260246000fd5b60808152600080865481600182811c915080831680612ecc57607f831692505b6020808410821415612eec57634e487b7160e01b86526022600452602486fd5b6080880184905260a08801828015612f0b5760018114612f1c57612f47565b60ff19871682528282019750612f47565b60008e81526020902060005b87811015612f4157815484820152908601908401612f28565b83019850505b50508701999099525050506001600160a01b0394909416604083015250606001529291505056fe7abd75c194a519c7fa820e973913b881d125f00b25a62aa1b242f1a3be7313a0a26469706673582212209c218326669dce6d28d51a19bd7800d412fdc09754adb7f9b50064155c64954064736f6c634300080c0033

Deployed ByteCode

0x608060405234801561001057600080fd5b50600436106101695760003560e01c806215d5b11461016e57806311a800bc1461019e57806315c4b528146101b1578063197ad63a146101c65780632785bf16146101dc5780632ad9b698146101ef5780632fd4af05146101f8578063303238a21461020b578063378dc3dc1461021357806351ad5a971461021c57806356df8adb146102255780635a5f219414610238578063636049a71461024b578063715018a61461025e5780637e6b26cf146102665780637f3619281461027957806382bc07e61461028c57806386f7a3941461029557806389304061146102a85780638da5cb5b146102bb57806399016142146102c3578063ad93b0ab146102d6578063b361b00f146102ff578063b994a7c514610312578063bdc330cb1461031b578063c32be42814610330578063e361349d14610362578063f2fde38b1461036a578063f9714c0c1461037d578063ffe8e37214610386575b600080fd5b606654610181906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101816101ac36600461295a565b610390565b6101c46101bf366004612973565b6103ab565b005b6101ce61095c565b604051908152602001610195565b6101c46101ea3660046129ef565b610984565b6101ce606e5481565b6101c4610206366004612a4c565b610bae565b6101ce60c881565b6101ce606c5481565b6101ce606f5481565b6101c4610233366004612a76565b610be7565b607354610181906001600160a01b031681565b6101c461025936600461295a565b611060565b6101c461109b565b6101c461027436600461295a565b6110d6565b6101c4610287366004612a98565b61110e565b6101ce60695481565b6101ce6102a3366004612a98565b6111d1565b606554610181906001600160a01b031681565b610181611222565b6101c46102d1366004612ab3565b611231565b6101ce6102e4366004612a98565b6001600160a01b031660009081526075602052604090205490565b6101ce680a18f07d736b90be55601d1b81565b620f42406101ce565b6103236112e8565b6040516101959190612b46565b61034361033e36600461295a565b611376565b604080516001600160a01b039093168352602083019190915201610195565b6070546101ce565b6101c4610378366004612a98565b6113ae565b6101ce606d5481565b6101ce620f424081565b6000908152607460205260409020546001600160a01b031690565b600054610100900460ff166103c65760005460ff16156103ca565b303b155b6104325760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b600054610100900460ff16158015610454576000805461ffff19166101011790555b680a18f07d736b90be55601d1b8260600135111561048557604051633f23f64d60e21b815260040160405180910390fd5b60a08201356104a75760405163e32538b760e01b815260040160405180910390fd5b608082013515806104c75750680a18f07d736b90be55601d1b8260800135115b156104e557604051630e9165d160e01b815260040160405180910390fd5b60006104f76060840160408501612a98565b6001600160a01b0316141561051f5760405163ae2b6bdd60e01b815260040160405180910390fd5b600061052e6020840184612a98565b6001600160a01b0316141561055657604051635cd2fa1560e01b815260040160405180910390fd5b610564610120830183612b79565b9050610574610100840184612b79565b9050146105945760405163d88d538b60e01b815260040160405180910390fd5b60006105a66040840160208501612a98565b6001600160a01b031614156105ce5760405163592ca6df60e11b815260040160405180910390fd5b6105d661144b565b336105e46020840184612a98565b6001600160a01b031614610602576106026103786020840184612a98565b61060f8260e0013561147a565b61061c8260c001356114d2565b61062c6040830160208401612a98565b606580546001600160a01b0319166001600160a01b039290921691821790556040805163bdc330cb60e01b8152905163bdc330cb916004808201926000929091908290030181865afa158015610686573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106ae9190810190612bd8565b80516106c2916067916020909101906128c1565b506106d36060830160408401612a98565b606680546001600160a01b0319166001600160a01b03929092169190911790556107036060830160408401612a98565b607360006101000a8154816001600160a01b0302191690836001600160a01b031602179055506107bc6067805461073990612c79565b80601f016020809104026020016040519081016040528092919081815260200182805461076590612c79565b80156107b25780601f10610787576101008083540402835291602001916107b2565b820191906000526020600020905b81548152906001019060200180831161079557829003601f168201915b5050505050611507565b80516107d0916068916020909101906128c1565b506060820135606c556080820135606e5560005b6107f2610100840184612b79565b905081101561086d5761085b61080c610100850185612b79565b8381811061081c5761081c612cb4565b90506020020160208101906108319190612a98565b61083f610120860186612b79565b8481811061084f5761084f612cb4565b90506020020135611669565b8061086581612ce0565b9150506107e4565b50606c5415610946576000610883606c5461196f565b60408051606081018252606c54808252602080830185815260a08901358486019081526000808052606a90935293517f6021fa82de881996a3e5fd2d032f74dfe72746b8a66c5510d4ab1a3cb789150755517f6021fa82de881996a3e5fd2d032f74dfe72746b8a66c5510d4ab1a3cb78915085591517f6021fa82de881996a3e5fd2d032f74dfe72746b8a66c5510d4ab1a3cb7891509559151929350600080516020612f6f8339815191529261093c92908590612cfb565b60405180910390a1505b8015610958576000805461ff00191690555b5050565b6000606d546069541061097e57606d546069546109799190612d11565b905090565b50600090565b6066546001600160a01b0316336001600160a01b0316141580156109cf57506073546001600160a01b031615806109cf57506073546001600160a01b0316336001600160a01b031614155b156109ed5760405163b1b4decb60e01b815260040160405180910390fd5b600080610a338285858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b92509050828210610a5757604051638397fe2960e01b815260040160405180910390fd5b82821015610ba8576000610aa48386868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b935090506000600380831690811115610abf57610abf612d28565b9050600282901c80610ae457604051632709375d60e21b815260040160405180910390fd5b6000826003811115610af857610af8612d28565b1415610b1257610b0b8486838a8a611b3f565b9450610ba0565b6001826003811115610b2657610b26612d28565b1415610b3957610b0b8486838a8a611c3d565b6002826003811115610b4d57610b4d612d28565b1415610b6057610b0b8486838a8a611d2d565b6003826003811115610b7457610b74612d28565b1415610b8757610b0b8486838a8a611e2b565b6040516325cb653b60e21b815260040160405180910390fd5b505050610a57565b50505050565b33610bb7611222565b6001600160a01b031614610bdd5760405162461bcd60e51b815260040161042990612d3e565b6109588282611669565b6066546001600160a01b0316336001600160a01b031614158015610c3257506073546001600160a01b03161580610c3257506073546001600160a01b0316336001600160a01b031614155b15610c505760405163b1b4decb60e01b815260040160405180910390fd5b606954610c5e906001612d73565b8214610c7c57604051622cbe4b60e91b815260040160405180910390fd5b80610c9a57604051630bff745560e11b815260040160405180910390fd5b6000610ca461095c565b90508015610cdc57606a6000610cbb600184612d11565b81526020810191909152604001600090812081815560018101829055600201555b606554604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd9160048083019260209291908290030181865afa158015610d26573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d4a9190612d8b565b607254606e5460775460715493945091929091600091610d6b908590612d73565b610d759190612d73565b905083811115610da2576002610d8b8583612d11565b610d959190612da4565b610d9f9083612d73565b91505b6000606e54118015610db657506000606f54115b15610dea57620f4240606f54606e54610dcf9190612dc6565b610dd99190612da4565b606e54610de69190612d11565b606e555b60008215610e3957610dfb8361196f565b6040805160608101825285815260208082018481528284018c815260008e8152606a9093529390912091518255516001820155905160029091015590505b600080516020612f6f833981519152888483604051610e5a93929190612cfb565b60405180910390a160698890556071859055600060728190556077558015801590610e855750600084115b15611056576000805b607054811015610edc5760708181548110610eab57610eab612cb4565b90600052602060002090600202016001015482610ec89190612d73565b915080610ed481612ce0565b915050610e8e565b506000610eec82620f4240612d11565b610ef9620f424088612dc6565b610f039190612da4565b90506000610f118783612d11565b905060005b60705481108015610f275750600082115b15611051576000620f424060708381548110610f4557610f45612cb4565b90600052602060002090600202016001015485610f629190612dc6565b610f6c9190612da4565b9050600081118015610f7e5750828111155b1561103e5780607754610f919190612d73565b607755606554607080546001600160a01b039092169163ab89013b91309186908110610fbf57610fbf612cb4565b60009182526020909120600290910201546040516001600160e01b031960e085901b168152610ffd92916001600160a01b0316908690600401612de5565b600060405180830381600087803b15801561101757600080fd5b505af115801561102b573d6000803e3d6000fd5b50505050808361103b9190612d11565b92505b508061104981612ce0565b915050610f16565b505050505b5050505050505050565b33611069611222565b6001600160a01b03161461108f5760405162461bcd60e51b815260040161042990612d3e565b611098816114d2565b50565b336110a4611222565b6001600160a01b0316146110ca5760405162461bcd60e51b815260040161042990612d3e565b6110d46000611f19565b565b336110df611222565b6001600160a01b0316146111055760405162461bcd60e51b815260040161042990612d3e565b6110988161147a565b33611117611222565b6001600160a01b03161461113d5760405162461bcd60e51b815260040161042990612d3e565b6001600160a01b0381166111645760405163ae2b6bdd60e01b815260040160405180910390fd5b60668054607380546001600160a01b038084166001600160a01b0319928316811790935592169184169182179092556040805191825260208201929092527fea601ecdff79475b71fb4d1545a7ebca8349b2af1ca1ff06dae26d8598067e1291015b60405180910390a150565b6000806111dc61095c565b6001600160a01b0384166000908152606b60205260409020549091508111156112055792915050565b50506001600160a01b03166000908152606b602052604090205490565b6033546001600160a01b031690565b600061127585878686868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611f6b92505050565b6066549091506001600160a01b038083169116148015906112b757506073546001600160a01b031615806112b757506073546001600160a01b03828116911614155b156112d557604051636db7f57960e11b815260040160405180910390fd5b6112e086868661200d565b505050505050565b606780546112f590612c79565b80601f016020809104026020016040519081016040528092919081815260200182805461132190612c79565b801561136e5780601f106113435761010080835404028352916020019161136e565b820191906000526020600020905b81548152906001019060200180831161135157829003601f168201915b505050505081565b6070818154811061138657600080fd5b6000918252602090912060029091020180546001909101546001600160a01b03909116915082565b336113b7611222565b6001600160a01b0316146113dd5760405162461bcd60e51b815260040161042990612d3e565b6001600160a01b0381166114425760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610429565b61109881611f19565b600054610100900460ff166114725760405162461bcd60e51b815260040161042990612e26565b6110d46122ee565b620f4240811061149d576040516356f1e3d760e11b815260040160405180910390fd5b606f8190556040518181527f3fc8c6c6a50072e9b4dcce50dc8a4f21877704651dff1c72f102afde8e08016b906020016111c6565b606d8190556040518181527f9b9d9ee399388905981700b7c8de8ca8853dbf397b77d509d7ed7a3d4dff52a6906020016111c6565b60606000829050600081516001600160401b0381111561152957611529612bc2565b6040519080825280601f01601f191660200182016040528015611553576020820181803683370190505b50905060005b825181101561166157604183828151811061157657611576612cb4565b016020015160f81d128015906115a65750605a83828151811061159b5761159b612cb4565b016020015160f81d13155b15611608578281815181106115bd576115bd612cb4565b602001015160f81c60f81b60f81c60206115d79190612e71565b60f81b8282815181106115ec576115ec612cb4565b60200101906001600160f81b031916908160001a90535061164f565b82818151811061161a5761161a612cb4565b602001015160f81c60f81b82828151811061163757611637612cb4565b60200101906001600160f81b031916908160001a9053505b8061165981612ce0565b915050611559565b509392505050565b620f4240811061168c576040516356f1e3d760e11b815260040160405180910390fd5b801580159061169e575060705460c811155b156116ca5760705460405163dbeb32b760e01b8152600481019190915260c86024820152604401610429565b6000805b607054811015611846576000607082815481106116ed576116ed612cb4565b60009182526020918290206040805180820190915260029092020180546001600160a01b0390811680845260019092015493830193909352909250908616141561183357836118045760705461174590600190612d11565b82146117c5576070805461175b90600190612d11565b8154811061176b5761176b612cb4565b90600052602060002090600202016070838154811061178c5761178c612cb4565b60009182526020909120825460029092020180546001600160a01b0319166001600160a01b039092169190911781556001918201549101555b60708054806117d6576117d6612e96565b60008281526020812060026000199093019283020180546001600160a01b031916815560010155905561182e565b836070838154811061181857611818612cb4565b9060005260206000209060020201600101819055505b600192505b508061183e81612ce0565b9150506116ce565b50806118ea578161185657505050565b604080518082019091526001600160a01b038481168252602082018481526070805460018101825560009190915292517f8f6b23ffa15f0465e3176e15ca644cf24f86dc1312fe715484e3c4aead5eb78b600290940293840180546001600160a01b0319169190931617909155517f8f6b23ffa15f0465e3176e15ca644cf24f86dc1312fe715484e3c4aead5eb78c909101555b6118f261231e565b6069546000908152606a602052604090206072541580156119135750805415155b156119295780546119239061196f565b60018201555b6040518381526001600160a01b0385169033907f67efa3716a83d716fc277019d454659a46cd1a11e7f4518b63ce585d5e6713e59060200160405180910390a350505050565b60008060005b6070548110156119ec576119ce846070838154811061199657611996612cb4565b60009182526020918290206040805180820190915260029092020180546001600160a01b031682526001015491810191909152612396565b6119d89083612d73565b9150806119e481612ce0565b915050611975565b5092915050565b600080835160001415611a195760405163517aa30d60e01b815260040160405180910390fd5b6000611a2585856123bd565b905060808116611a69576000611a3c856001612d73565b9050611a49868683612419565b607f8216611a58866001612d73565b8160ff169150935093505050611b38565b608060c082161415611add576000611a82856002612d73565b9050611a8f868683612419565b6000611aa6611a9f876001612d73565b88906123bd565b60ff16905080611abb603f8516610100612dc6565b611ac59190612d73565b611ad0906080612d73565b8294509450505050611b38565b6000611af0600183811c601f1690612e71565b60ff1690506000611b018287612d73565b611b0c906001612d73565b9050611b19878783612419565b60018683018801810151600890930281011b6000190190911693509150505b9250929050565b6000805b84811015611c32576000611b908786868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b975090506000611b9f82610390565b90506001600160a01b038116611bc857604051638048717f60e01b815260040160405180910390fd5b6000611c0d8988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b99509050611c1c8a838361200d565b5050508080611c2a90612ce0565b915050611b43565b509395945050505050565b6000805b84811015611c32576000611c8e8786868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506124619050565b90506001600160a01b038116611cb757604051638048717f60e01b815260040160405180910390fd5b611cc2601488612d73565b96506000611d098887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b98509050611d1889838361200d565b50508080611d2590612ce0565b915050611c41565b600080611d738685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b9650905060005b85811015611e1f576000611dc78887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b985090506000611dd682610390565b90506001600160a01b038116611dff57604051638048717f60e01b815260040160405180910390fd5b611e0a8a828661200d565b50508080611e1790612ce0565b915050611d7a565b50949695505050505050565b600080611e718685858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506119f39050565b9650905060005b85811015611e1f576000611ec58887878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092939250506124619050565b90506001600160a01b038116611eee57604051638048717f60e01b815260040160405180910390fd5b611ef9601489612d73565b9750611f0689828561200d565b5080611f1181612ce0565b915050611e78565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000806068858786604051602001611f869493929190612eac565b6040516020818303038152906040528051906020012090506000611ff6826040517b0ca2ba3432b932bab69029b4b3b732b21026b2b9b9b0b3b29d05199960211b6020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b905061200281856124c6565b979650505050505050565b6001600160a01b038216612034576040516362ce452360e11b815260040160405180910390fd5b8061205257604051631d4a43d160e31b815260040160405180910390fd5b6001600160a01b0382166000908152606b602052604090205483101561208b57604051636ccebf8760e01b815260040160405180910390fd5b6069548311156120ae57604051630312c8bb60e61b815260040160405180910390fd5b60006120b9836111d1565b9050808410156120dc576040516339cf91f560e01b815260040160405180910390fd5b8015806120e857508084145b806120fb5750336001600160a01b038416145b8061211957506066546001600160a01b0316336001600160a01b0316145b6121365760405163606133af60e11b815260040160405180910390fd5b6000848152606a6020908152604091829020825160608101845281548082526001830154938201939093526002909101549281019290925261218b5760405163e8c06a8b60e01b815260040160405180910390fd5b60408101516121ad576040516356041c3560e01b815260040160405180910390fd5b6000816040015184836020015184600001516121c99190612d11565b6121d39190612dc6565b6121dd9190612da4565b9050806121fd576040516385ffe97960e01b815260040160405180910390fd5b8060725461220b9190612d73565b607255612219866001612d73565b6001600160a01b0386166000908152606b602052604090205561223b856124e2565b846001600160a01b03167f05c0dd48c9c829cad200bd1db15fa75fb03cae88add3bd16cb8899cc39412f7487868460405161227893929190612cfb565b60405180910390a260655460405163ab89013b60e01b81526001600160a01b039091169063ab89013b906122b490309089908690600401612de5565b600060405180830381600087803b1580156122ce57600080fd5b505af11580156122e2573d6000803e3d6000fd5b50505050505050505050565b600054610100900460ff166123155760405162461bcd60e51b815260040161042990612e26565b6110d433611f19565b6000805b607054811015612370576070818154811061233f5761233f612cb4565b9060005260206000209060020201600101548261235c9190612d73565b91508061236881612ce0565b915050612322565b50620f424081106110985760405162202b6360ea1b815260048101829052602401610429565b6000620f42408260200151846123ac9190612dc6565b6123b69190612da4565b9392505050565b60006123ca826001612d73565b835110156124105760405162461bcd60e51b8152602060048201526013602482015272746f55696e74385f6f75744f66426f756e647360681b6044820152606401610429565b50016001015190565b8181101561243a57604051635407ffff60e11b815260040160405180910390fd5b808351101561245c5760405163517aa30d60e01b815260040160405180910390fd5b505050565b600061246e826014612d73565b835110156124b65760405162461bcd60e51b8152602060048201526015602482015274746f416464726573735f6f75744f66426f756e647360581b6044820152606401610429565b500160200151600160601b900490565b60008060006124d58585612582565b91509150611661816125ef565b6001600160a01b038116600090815260756020526040902054611098576001607660008282546125129190612d73565b9091555050607680546001600160a01b03831660008181526075602090815260408083208590559382526074905282812080546001600160a01b0319168317905592549151919290917fefd1ddef00b1051abc144c2e895de70a10dbbc3ad8985118c74c15e40e3d391f9190a350565b6000808251604114156125b95760208301516040840151606085015160001a6125ad878285856127a5565b94509450505050611b38565b8251604014156125e357602083015160408401516125d8868383612888565b935093505050611b38565b50600090506002611b38565b600081600481111561260357612603612d28565b141561260c5750565b600181600481111561262057612620612d28565b14156126695760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b6044820152606401610429565b600281600481111561267d5761267d612d28565b14156126cb5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610429565b60038160048111156126df576126df612d28565b14156127385760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610429565b600481600481111561274c5761274c612d28565b14156110985760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b6064820152608401610429565b6000806fa2a8918ca85bafe22016d0b997e4df60600160ff1b038311156127d2575060009050600361287f565b8460ff16601b141580156127ea57508460ff16601c14155b156127fb575060009050600461287f565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa15801561284f573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166128785760006001925092505061287f565b9150600090505b94509492505050565b6000806001600160ff1b038316816128a560ff86901c601b612d73565b90506128b3878288856127a5565b935093505050935093915050565b8280546128cd90612c79565b90600052602060002090601f0160209004810192826128ef5760008555612935565b82601f1061290857805160ff1916838001178555612935565b82800160010185558215612935579182015b8281111561293557825182559160200191906001019061291a565b50612941929150612945565b5090565b5b808211156129415760008155600101612946565b60006020828403121561296c57600080fd5b5035919050565b60006020828403121561298557600080fd5b81356001600160401b0381111561299b57600080fd5b820161014081850312156123b657600080fd5b60008083601f8401126129c057600080fd5b5081356001600160401b038111156129d757600080fd5b602083019150836020828501011115611b3857600080fd5b60008060208385031215612a0257600080fd5b82356001600160401b03811115612a1857600080fd5b612a24858286016129ae565b90969095509350505050565b80356001600160a01b0381168114612a4757600080fd5b919050565b60008060408385031215612a5f57600080fd5b612a6883612a30565b946020939093013593505050565b60008060408385031215612a8957600080fd5b50508035926020909101359150565b600060208284031215612aaa57600080fd5b6123b682612a30565b600080600080600060808688031215612acb57600080fd5b85359450612adb60208701612a30565b93506040860135925060608601356001600160401b03811115612afd57600080fd5b612b09888289016129ae565b969995985093965092949392505050565b60005b83811015612b35578181015183820152602001612b1d565b83811115610ba85750506000910152565b6020815260008251806020840152612b65816040850160208701612b1a565b601f01601f19169190910160400192915050565b6000808335601e19843603018112612b9057600080fd5b8301803591506001600160401b03821115612baa57600080fd5b6020019150600581901b3603821315611b3857600080fd5b634e487b7160e01b600052604160045260246000fd5b600060208284031215612bea57600080fd5b81516001600160401b0380821115612c0157600080fd5b818401915084601f830112612c1557600080fd5b815181811115612c2757612c27612bc2565b604051601f8201601f19908116603f01168101908382118183101715612c4f57612c4f612bc2565b81604052828152876020848701011115612c6857600080fd5b612002836020830160208801612b1a565b600181811c90821680612c8d57607f821691505b60208210811415612cae57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612cf457612cf4612cca565b5060010190565b9283526020830191909152604082015260600190565b600082821015612d2357612d23612cca565b500390565b634e487b7160e01b600052602160045260246000fd5b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60008219821115612d8657612d86612cca565b500190565b600060208284031215612d9d57600080fd5b5051919050565b600082612dc157634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615612de057612de0612cca565b500290565b6001600160a01b039384168152919092166020820152604081019190915260a060608201819052600090820181905260c06080830181905282015260e00190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600060ff821660ff84168060ff03821115612e8e57612e8e612cca565b019392505050565b634e487b7160e01b600052603160045260246000fd5b60808152600080865481600182811c915080831680612ecc57607f831692505b6020808410821415612eec57634e487b7160e01b86526022600452602486fd5b6080880184905260a08801828015612f0b5760018114612f1c57612f47565b60ff19871682528282019750612f47565b60008e81526020902060005b87811015612f4157815484820152908601908401612f28565b83019850505b50508701999099525050506001600160a01b0394909416604083015250606001529291505056fe7abd75c194a519c7fa820e973913b881d125f00b25a62aa1b242f1a3be7313a0a26469706673582212209c218326669dce6d28d51a19bd7800d412fdc09754adb7f9b50064155c64954064736f6c634300080c0033