false
false
0

Contract Address Details

0xCd270A5c6fD450e8a92191175202B85a7B0167A4

Contract Name
Ftso
Creator
0x4598a6–d10d29 at 0xec5cfe–60eb22
Balance
0 FLR
Tokens
Fetching tokens...
Transactions
0 Transactions
Transfers
0 Transfers
Gas Used
Fetching gas used...
Last Balance Update
22542325
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
Contract name:
Ftso




Optimization enabled
true
Compiler version
v0.7.6+commit.7338295f




Optimization runs
200
Verified at
2022-07-13T21:11:29.964390Z

Constructor Arguments

0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000500000000000000000000000010000000000000000000000000000000000000030000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028940000000000000000000000000000000000000000000000000000000062cf1e4600000000000000000000000000000000000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc00000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000346494c0000000000000000000000000000000000000000000000000000000000

Arg [0] (string) : FIL
Arg [1] (uint256) : 5
Arg [2] (address) : 0x1000000000000000000000000000000000000003
Arg [3] (address) : 0x1d80c49bbbcd1c0911346656b529df9e5c2f783d
Arg [4] (address) : 0xab7c7da1ff2b25d0867908ddfa52827570e02894
Arg [5] (uint256) : 1657740870
Arg [6] (uint256) : 180
Arg [7] (uint256) : 90
Arg [8] (uint128) : 0
Arg [9] (uint256) : 1500
Arg [10] (uint256) : 200

              

./contracts/ftso/implementation/Ftso.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;

import "../../token/interface/IIVPToken.sol";
import "../interface/IIFtso.sol";
import "../lib/FtsoEpoch.sol";
import "../lib/FtsoVote.sol";
import "../lib/FtsoMedian.sol";
import "../../userInterfaces/IPriceSubmitter.sol";


/**
 * @title A contract implementing Flare Time Series Oracle
 */
contract Ftso is IIFtso {
    using FtsoEpoch for FtsoEpoch.State;
    using SafeCast for uint256;

    // errors
    string internal constant ERR_NOT_ACTIVE = "FTSO not active";
    string internal constant ERR_ALREADY_ACTIVATED = "FTSO already activated";
    string internal constant ERR_NO_ACCESS = "Access denied";
    string internal constant ERR_PRICE_TOO_HIGH = "Price too high";
    string internal constant ERR_PRICE_REVEAL_FAILURE = "Reveal period not active";
    string internal constant ERR_EPOCH_FINALIZATION_FAILURE = "Epoch not ready for finalization";
    string internal constant ERR_EPOCH_ALREADY_FINALIZED = "Epoch already finalized";
    string internal constant ERR_EPOCH_NOT_INITIALIZED_FOR_REVEAL = "Epoch not initialized for reveal";
    string internal constant ERR_EPOCH_DATA_NOT_AVAILABLE = "Epoch data not available";
    string internal constant ERR_INVALID_PRICE_EPOCH_PARAMETERS = "Invalid price epoch parameters";
    
    
    // storage
    uint256 public immutable priceDeviationThresholdBIPS;   // threshold for price deviation between consecutive epochs
    uint256 public immutable priceEpochCyclicBufferSize;
    bool public override active;                    // activation status of FTSO
    string public override symbol;                  // asset symbol that identifies FTSO

    // number of decimal places in Asset USD price
    // note that the actual USD price is the integer value divided by 10^ASSET_PRICE_USD_DECIMALS
    // solhint-disable-next-line var-name-mixedcase
    uint256 public immutable ASSET_PRICE_USD_DECIMALS;

    uint128 internal assetPriceUSD;                             // current asset USD price
    uint128 internal assetPriceTimestamp;                       // time when price was updated
    uint128 internal assetTrustedProvidersPriceUSD;             // current asset USD price from trusted providers
    uint128 internal assetTrustedProvidersPriceTimestamp;       // time when price from trusted providers was updated
    PriceFinalizationType internal assetPriceFinalizationType;  // price finalization type (uint8)
    uint240 internal lastPriceEpochFinalizationTimestamp;       // last price epoch finalization timestamp
    PriceFinalizationType internal lastPriceEpochFinalizationType;  // last price epoch finalization type (uint8)
    FtsoEpoch.State internal epochs;                            // epoch storage

    // immutable settings
    uint256 private immutable firstEpochStartTs;    // start timestamp of the first epoch instance
    uint256 private immutable submitPeriodSeconds;  // duration of price submission for an epoch instance
    uint256 private immutable revealPeriodSeconds;  // duration of price reveal for an epoch instance

    // external contracts
    IIVPToken public immutable override wNat;       // wrapped native token
    address immutable public override ftsoManager;  // FTSO manager contract
    IPriceSubmitter public immutable priceSubmitter;// Price submitter contract

    IIVPToken[] public assets;                      // array of assets
    IIFtso[] public assetFtsos;                     // FTSOs for assets (for a multi-asset FTSO)

    // Revert strings get inlined and take a lot of contract space
    // Calling them from auxiliary functions removes used space
    modifier whenActive {
        if (!active) {
            revertNotActive();
        }
        _;
    }

    modifier onlyFtsoManager {
        if (msg.sender != ftsoManager) {
            revertNoAccess();
        }
        _;
    }

    modifier onlyPriceSubmitter {
        if (msg.sender != address(priceSubmitter)) {
            revertNoAccess();
        }
        _;
    }

    constructor(
        string memory _symbol,
        uint256 _decimals,
        IPriceSubmitter _priceSubmitter,
        IIVPToken _wNat,
        address _ftsoManager,
        uint256 _firstEpochStartTs,
        uint256 _submitPeriodSeconds,
        uint256 _revealPeriodSeconds,
        uint128 _initialPriceUSD,
        uint256 _priceDeviationThresholdBIPS,
        uint256 _cyclicBufferSize
    )
    {
        symbol = _symbol;
        ASSET_PRICE_USD_DECIMALS = _decimals;
        priceSubmitter = _priceSubmitter;
        wNat = _wNat;
        ftsoManager = _ftsoManager;
        firstEpochStartTs = _firstEpochStartTs;
        submitPeriodSeconds = _submitPeriodSeconds;
        revealPeriodSeconds = _revealPeriodSeconds;
        assetPriceUSD = _initialPriceUSD;
        assetPriceTimestamp = uint128(block.timestamp); // no overflow
        priceDeviationThresholdBIPS = _priceDeviationThresholdBIPS;
        priceEpochCyclicBufferSize = _cyclicBufferSize;
    }

    /**
     * @notice Reveals submitted price during epoch reveal period
     * @param _voter                Voter address
     * @param _epochId              Id of the epoch in which the price hash was submitted
     * @param _price                Submitted price in USD
     * @notice The hash of _price and _random must be equal to the submitted hash
     * @notice Emits PriceRevealed event
     */
    function revealPriceSubmitter(
        address _voter,
        uint256 _epochId,
        uint256 _price,
        uint256 _voterWNatVP
    ) 
        external override
        whenActive
        onlyPriceSubmitter
    {
        _revealPrice(_voter, _epochId, _price, _voterWNatVP);
    }

    /**
     * @notice Computes epoch price based on gathered votes
     * @param _epochId              Id of the epoch
     * @param _returnRewardData     Parameter that determines if the reward data is returned
     * @return _eligibleAddresses   List of addresses eligible for reward
     * @return _natWeights          List of native token weights corresponding to the eligible addresses
     * @return _natWeightsSum       Sum of weights in _natWeights
     */
    function finalizePriceEpoch(
        uint256 _epochId,
        bool _returnRewardData
    ) 
        external override
        onlyFtsoManager 
        returns (
            address[] memory _eligibleAddresses,
            uint256[] memory _natWeights,
            uint256 _natWeightsSum
        ) 
    {
        FtsoEpoch.Instance storage epoch = _getEpochForFinalization(_epochId);
        epoch.initializedForReveal = false; // set back to false for next usage

        uint256 natTurnout = 0;
        if (epoch.circulatingSupplyNat > 0) {
            // no overflow - epoch.accumulatedVotePowerNat is the sum of all wNats of voters for given vote power block
            natTurnout = epoch.accumulatedVotePowerNat * FtsoEpoch.BIPS100 / epoch.circulatingSupplyNat;
        }

        if (epoch.fallbackMode || natTurnout <= epoch.lowNatTurnoutThresholdBIPS) {
            if (!epoch.fallbackMode) {
                emit LowTurnout(_epochId, natTurnout, epoch.lowNatTurnoutThresholdBIPS, block.timestamp);
            }
            _medianFinalizePriceEpoch(_epochId, epoch, false);

            // return empty reward data
            return (_eligibleAddresses, _natWeights, _natWeightsSum);
        }

        // finalizationType = PriceFinalizationType.WEIGHTED_MEDIAN
        // extract data from epoch votes to memory
        uint256[] memory price;
        uint256[] memory weight;
        uint256[] memory weightNat;
        (price, weight, weightNat) = _readVotes(epoch);

        // compute weighted median and truncated quartiles
        uint256[] memory index;
        FtsoMedian.Data memory data;
        (index, data) = FtsoMedian._computeWeighted(price, weight);

        // check price deviation
        if (epochs._getPriceDeviation(_epochId, data.finalMedianPrice, priceEpochCyclicBufferSize)
            > 
            priceDeviationThresholdBIPS)
        {
            // revert to median price calculation
            _medianFinalizePriceEpoch(_epochId, epoch, false);
            // return empty reward data
            return (_eligibleAddresses, _natWeights, _natWeightsSum);
        }

        // store epoch results
        epoch.finalizationType = PriceFinalizationType.WEIGHTED_MEDIAN;
        epoch.price = data.finalMedianPrice; 
        
        // update price
        assetPriceUSD = uint128(data.finalMedianPrice); // no overflow
        assetPriceTimestamp = uint128(block.timestamp); // no overflow
        assetPriceFinalizationType = PriceFinalizationType.WEIGHTED_MEDIAN;
        lastPriceEpochFinalizationTimestamp = uint240(block.timestamp); // no overflow
        lastPriceEpochFinalizationType = PriceFinalizationType.WEIGHTED_MEDIAN;

        // update trusted providers price
        if (epoch.trustedVotes.length > 0) {
            assetTrustedProvidersPriceUSD = uint128(FtsoMedian._computeSimple(epoch.trustedVotes)); // no overflow
            assetTrustedProvidersPriceTimestamp = uint128(block.timestamp); // no overflow
        }
        
        // return reward data if requested
        bool rewardedFtso = false;
        if (_returnRewardData) {
            uint256 random = _getRandom(_epochId);
            (_eligibleAddresses, _natWeights, _natWeightsSum) = 
                _readRewardData(epoch, data, random, index, weightNat);
            if (_eligibleAddresses.length > 0) {
                rewardedFtso = true;
            }
        }

        // allow saving some informational data (no-op here)
        _writeEpochPriceData(_epochId, data, index, rewardedFtso);

        // inform about epoch result
        emit PriceFinalized(_epochId, epoch.price, rewardedFtso, 
            data.quartile1Price, data.quartile3Price, epoch.finalizationType,
            block.timestamp);

        epoch.fallbackMode = false; // set back to false for next usage
    }

    /**
     * @notice Forces finalization of price epoch calculating median price from trusted addresses
     * @param _epochId              Id of the epoch to finalize
     * @dev Used as a fallback method if epoch finalization is failing
     */
    function fallbackFinalizePriceEpoch(uint256 _epochId) external override onlyFtsoManager {
        FtsoEpoch.Instance storage epoch = _getEpochForFinalization(_epochId);
        epoch.initializedForReveal = false; // set back to false for next usage
        _medianFinalizePriceEpoch(_epochId, epoch, true);
    }

    /**
     * @notice Forces finalization of price epoch - only called when exception happened
     * @param _epochId              Id of the epoch to finalize
     * @dev Used as a fallback method if epoch finalization is failing
     */
    function forceFinalizePriceEpoch(uint256 _epochId) external override onlyFtsoManager {
        FtsoEpoch.Instance storage epoch = _getEpochForFinalization(_epochId);
        epoch.initializedForReveal = false; // set back to false for next usage
        _forceFinalizePriceEpoch(_epochId, epoch, true);
    }

    /**
     * @notice Initializes ftso immutable settings and activates oracle
     * @param _firstEpochStartTs    Timestamp of the first epoch as seconds from unix epoch
     * @param _submitPeriodSeconds  Duration of epoch submission period in seconds
     * @param _revealPeriodSeconds  Duration of epoch reveal period in seconds
     */
    function activateFtso(
        uint256 _firstEpochStartTs,
        uint256 _submitPeriodSeconds,
        uint256 _revealPeriodSeconds
    ) 
        external override
        onlyFtsoManager
    {
        require(!active, ERR_ALREADY_ACTIVATED);
        require(firstEpochStartTs == _firstEpochStartTs, ERR_INVALID_PRICE_EPOCH_PARAMETERS);
        require(submitPeriodSeconds == _submitPeriodSeconds, ERR_INVALID_PRICE_EPOCH_PARAMETERS);
        require(revealPeriodSeconds == _revealPeriodSeconds, ERR_INVALID_PRICE_EPOCH_PARAMETERS);
        active = true;
    }

    /**
     * @notice Deactivates oracle
     */
    function deactivateFtso() external override whenActive onlyFtsoManager {
        active = false;
    }

    /**
     * Updates initial/current Asset price, but only if not active
     */
    function updateInitialPrice(
        uint256 _initialPriceUSD,
        uint256 _initialPriceTimestamp
    ) 
        external override
        onlyFtsoManager
    {
        require(!active, ERR_ALREADY_ACTIVATED);
        assetPriceUSD = _initialPriceUSD.toUint128();
        assetPriceTimestamp = _initialPriceTimestamp.toUint128();
    }

    /**
     * @notice Sets configurable settings related to epochs
     * @param _maxVotePowerNatThresholdFraction         high threshold for native token vote power per voter
     * @param _maxVotePowerAssetThresholdFraction       high threshold for asset vote power per voter
     * @param _lowAssetUSDThreshold             threshold for low asset vote power
     * @param _highAssetUSDThreshold            threshold for high asset vote power
     * @param _highAssetTurnoutThresholdBIPS    threshold for high asset turnout
     * @param _lowNatTurnoutThresholdBIPS       threshold for low nat turnout
     * @param _trustedAddresses                 trusted addresses - use their prices if low nat turnout is not achieved
     * @dev Should never revert if called from ftso manager
     */
    function configureEpochs(
        uint256 _maxVotePowerNatThresholdFraction,
        uint256 _maxVotePowerAssetThresholdFraction,
        uint256 _lowAssetUSDThreshold,
        uint256 _highAssetUSDThreshold,
        uint256 _highAssetTurnoutThresholdBIPS,
        uint256 _lowNatTurnoutThresholdBIPS,
        address[] memory _trustedAddresses
    ) 
        external override
        onlyFtsoManager
    {
        epochs.maxVotePowerNatThresholdFraction = _maxVotePowerNatThresholdFraction;
        epochs.maxVotePowerAssetThresholdFraction = _maxVotePowerAssetThresholdFraction;
        epochs.lowAssetUSDThreshold = _lowAssetUSDThreshold;
        epochs.highAssetUSDThreshold = _highAssetUSDThreshold;
        epochs.highAssetTurnoutThresholdBIPS = _highAssetTurnoutThresholdBIPS;
        epochs.lowNatTurnoutThresholdBIPS = _lowNatTurnoutThresholdBIPS;

        // remove old addresses mapping
        uint256 len = epochs.trustedAddresses.length;
        for (uint256 i = 0; i < len; i++) {
            epochs.trustedAddressesMapping[epochs.trustedAddresses[i]] = false;
        }
        // set new addresses mapping
        len = _trustedAddresses.length;
        for (uint256 i = 0; i < len; i++) {
            epochs.trustedAddressesMapping[_trustedAddresses[i]] = true;
        }
        epochs.trustedAddresses = _trustedAddresses;
    }

    /**
     * @notice Sets current vote power block
     * @param _votePowerBlock       Vote power block
     */
    function setVotePowerBlock(uint256 _votePowerBlock) external override onlyFtsoManager {
        // votePowerBlock must be in the past to prevent flash loan attacks
        require(_votePowerBlock < block.number);
        require(_votePowerBlock < 2 ** 240);
        epochs.votePowerBlock = uint240(_votePowerBlock);
    }

    /**
     * @notice Sets asset for FTSO to operate as single-asset oracle
     * @param _asset               Asset
     */
    function setAsset(IIVPToken _asset) external override onlyFtsoManager {
        assetFtsos = [ IIFtso(this) ];
        assets = [ _asset ];
        epochs.assetNorm[_asset] = 10**_asset.decimals();
    }

    /**
     * @notice Sets an array of FTSOs for FTSO to operate as multi-asset oracle
     * @param _assetFtsos          Array of FTSOs
     * @dev FTSOs implicitly determine the FTSO assets
     */
    function setAssetFtsos(IIFtso[] memory _assetFtsos) external override onlyFtsoManager {
        assert(_assetFtsos.length > 0);
        assert(_assetFtsos.length > 1 || _assetFtsos[0] != this);
        assetFtsos = _assetFtsos;
        assets = new IIVPToken[](_assetFtsos.length);
        _refreshAssets();
    }

    /**
     * @notice Initializes current epoch instance for reveal
     * @param _circulatingSupplyNat     Epoch native token circulating supply
     * @param _fallbackMode             Current epoch in fallback mode
     */
    function initializeCurrentEpochStateForReveal(
        uint256 _circulatingSupplyNat,
        bool _fallbackMode
    )
        external override
        onlyFtsoManager
    {
        uint256 epochId = getCurrentEpochId();
        //slither-disable-next-line weak-prng // not used for random
        FtsoEpoch.Instance storage epoch = epochs.instance[epochId % priceEpochCyclicBufferSize];

        // reset values for current epoch 
        epoch.finalizationType = IFtso.PriceFinalizationType.NOT_FINALIZED;
        epoch.accumulatedVotePowerNat = 0;
        epoch.nextVoteIndex = 0;
        epoch.votePowerBlock = epochs.votePowerBlock;
        epoch.fallbackMode = _fallbackMode;
        epoch.epochId = epochId;
        delete epoch.trustedVotes;

        if (_fallbackMode) {
            return;
        }

        uint256[] memory assetVotePowers;
        uint256[] memory assetPrices;
        (, assetVotePowers, assetPrices) = _getAssetData();

        epochs._initializeInstanceForReveal(
            epoch,
            _circulatingSupplyNat,
            _getVotePowerAt(wNat, epochs.votePowerBlock),
            assets,
            assetVotePowers,
            assetPrices
        );

        emit PriceEpochInitializedOnFtso(epochId, _getEpochSubmitEndTime(epochId), block.timestamp);
    }
    
    /**
     * @notice Returns current epoch data
     * @return _firstEpochStartTs           First epoch start timestamp
     * @return _submitPeriodSeconds         Submit period in seconds
     * @return _revealPeriodSeconds         Reveal period in seconds
     */
    function getPriceEpochConfiguration() external view override 
        returns (
            uint256 _firstEpochStartTs,
            uint256 _submitPeriodSeconds,
            uint256 _revealPeriodSeconds
        )
    {
        return (
            firstEpochStartTs, 
            submitPeriodSeconds,
            revealPeriodSeconds
        );
    }

    /**
     * @notice Returns current configuration of epoch state
     */
    function epochsConfiguration() external view override 
        returns (
            uint256 _maxVotePowerNatThresholdFraction,
            uint256 _maxVotePowerAssetThresholdFraction,
            uint256 _lowAssetUSDThreshold,
            uint256 _highAssetUSDThreshold,
            uint256 _highAssetTurnoutThresholdBIPS,
            uint256 _lowNatTurnoutThresholdBIPS,
            address[] memory _trustedAddresses
        )
    {
        return (
            epochs.maxVotePowerNatThresholdFraction,
            epochs.maxVotePowerAssetThresholdFraction,
            epochs.lowAssetUSDThreshold,
            epochs.highAssetUSDThreshold,
            epochs.highAssetTurnoutThresholdBIPS,
            epochs.lowNatTurnoutThresholdBIPS,
            epochs.trustedAddresses
        );
    }

    /**
     * @notice Returns the FTSO asset
     * @dev asset is null in case of multi-asset FTSO
     */
    function getAsset() external view override returns (IIVPToken) {
        return assets.length == 1 && assetFtsos.length == 1 && assetFtsos[0] == this ?
            assets[0] : IIVPToken(address(0));
    }

    /**
     * @notice Returns the asset FTSOs
     * @dev AssetFtsos is not null only in case of multi-asset FTSO
     */
    function getAssetFtsos() external view override returns (IIFtso[] memory) {
        return assets.length == 1 && assetFtsos.length == 1 && assetFtsos[0] == this ?
            new IIFtso[](0) : assetFtsos;
    }

    /**
     * @notice Returns current asset price
     * @return _price               Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     * @return _timestamp           Time when price was updated for the last time
     */
    function getCurrentPrice() external view override returns (uint256 _price, uint256 _timestamp) {
        return (assetPriceUSD, assetPriceTimestamp);
    }

    /**
     * @notice Returns current asset price calculated from trusted providers
     * @return _price               Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     * @return _timestamp           Time when price was updated for the last time
     */
    function getCurrentPriceFromTrustedProviders() external view override 
        returns (
            uint256 _price,
            uint256 _timestamp
        )
    {
        return (assetTrustedProvidersPriceUSD, assetTrustedProvidersPriceTimestamp);
    }

    /**
     * @notice Returns current asset price details
     * @return _price                                   Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     * @return _priceTimestamp                          Time when price was updated for the last time
     * @return _priceFinalizationType                   Finalization type when price was updated for the last time
     * @return _lastPriceEpochFinalizationTimestamp     Time when last price epoch was finalized
     * @return _lastPriceEpochFinalizationType          Finalization type of last finalized price epoch
     */
    function getCurrentPriceDetails() external view override 
        returns (
            uint256 _price,
            uint256 _priceTimestamp,
            PriceFinalizationType _priceFinalizationType,
            uint256 _lastPriceEpochFinalizationTimestamp,
            PriceFinalizationType _lastPriceEpochFinalizationType
        )
    {
        return (
            assetPriceUSD,
            assetPriceTimestamp,
            assetPriceFinalizationType,
            lastPriceEpochFinalizationTimestamp,
            lastPriceEpochFinalizationType
        );
    }

    /**
     * @notice Returns asset price consented in specific epoch
     * @param _epochId              Id of the epoch
     * @return Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     */
    function getEpochPrice(uint256 _epochId) external view override returns (uint256) {
        return _getEpochInstance(_epochId).price;
    }

    /**
     * @notice Returns asset price submitted by voter in specific epoch
     * @param _epochId              Id of the epoch
     * @param _voter                Address of the voter
     * @return Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     */
    function getEpochPriceForVoter(uint256 _epochId, address _voter) external view override returns (uint256) {
        FtsoEpoch.Instance storage epoch = _getEpochInstance(_epochId);
        // only used off-chain, so loop should be ok
        uint256 voteInd = FtsoEpoch._findVoteOf(epoch, _voter);
        if (voteInd == 0) return 0;  // no vote from _voter
        return epoch.votes[voteInd - 1].price;
    }
    
    /**
     * @notice Returns current random number
     * @return Random number
     * @dev Should never revert
     */
    function getCurrentRandom() external view override returns (uint256) {
        uint256 currentEpochId = getCurrentEpochId();
        if (currentEpochId == 0) {
            return 0;
        }
        return _getRandom(currentEpochId - 1);
    }

    /**
     * @notice Returns random number of the specified epoch
     * @param _epochId Id of the epoch
     * @return Random number
     */
    function getRandom(uint256 _epochId) external view override returns (uint256) {
        return _getRandom(_epochId);
    }

    /**
     * @notice Returns current epoch data
     * @return _epochId                 Current epoch id
     * @return _epochSubmitEndTime      End time of the current epoch price submission as seconds from unix epoch
     * @return _epochRevealEndTime      End time of the current epoch price reveal as seconds from unix epoch
     * @return _votePowerBlock          Vote power block for the current epoch
     * @return _fallbackMode            Current epoch in fallback mode - only votes from trusted addresses will be used
     * @dev half-closed intervals - end time not included
     */
    function getPriceEpochData() external view override 
        returns (
            uint256 _epochId,
            uint256 _epochSubmitEndTime,
            uint256 _epochRevealEndTime,
            uint256 _votePowerBlock,
            bool _fallbackMode
        )
    {
        _epochId = getCurrentEpochId();
        _epochSubmitEndTime = _getEpochSubmitEndTime(_epochId);
        _epochRevealEndTime = _epochSubmitEndTime + revealPeriodSeconds;

        //slither-disable-next-line weak-prng // not used for random
        FtsoEpoch.Instance storage epoch = epochs.instance[_epochId % priceEpochCyclicBufferSize];
        _votePowerBlock = epoch.votePowerBlock;
        _fallbackMode = epoch.fallbackMode;
    }

    /**
     * @notice Returns parameters necessary for replicating vote weighting (used in VoterWhitelister).
     * @return _assets                  the list of assets that are accounted in vote
     * @return _assetMultipliers        weight multiplier of each asset in (multiasset) ftso
     * @return _totalVotePowerNat       total native token vote power at block
     * @return _totalVotePowerAsset     total combined asset vote power at block
     * @return _assetWeightRatio        ratio of combined asset vp vs. native token vp (in BIPS)
     * @return _votePowerBlock          vote powewr block for given epoch
     */
    function getVoteWeightingParameters() external view virtual override 
        returns (
            IIVPToken[] memory _assets,
            uint256[] memory _assetMultipliers,
            uint256 _totalVotePowerNat,
            uint256 _totalVotePowerAsset,
            uint256 _assetWeightRatio,
            uint256 _votePowerBlock
        )
    {
        _assets = assets;
        _votePowerBlock = epochs.votePowerBlock;
        uint256[] memory assetVotePowers = new uint256[](_assets.length);
        uint256[] memory assetPrices = new uint256[](_assets.length);
        for (uint256 i = 0; i < _assets.length; i++) {
            assetVotePowers[i] = address(_assets[i]) != address(0) ? _assets[i].totalVotePowerAt(_votePowerBlock) : 0;
            (assetPrices[i], ) = assetFtsos[i].getCurrentPrice();
        }
        uint256[] memory assetWeightedPrices = epochs._getAssetWeightedPrices(_assets, assetVotePowers, assetPrices);
        _assetMultipliers = epochs._getAssetVoteMultipliers(_assets, assetWeightedPrices);
        _totalVotePowerNat = wNat.totalVotePowerAt(_votePowerBlock);
        _totalVotePowerAsset = epochs._calculateAssetVotePower(_assets, assetVotePowers, assetWeightedPrices);
        _assetWeightRatio = epochs._getAssetBaseWeightRatio(_totalVotePowerAsset);
    }

    /**
     * @notice Returns wNat vote power for the specified owner and the given epoch id
     * @param _owner                Owner address
     * @param _epochId              Id of the epoch
     */
    function wNatVotePowerCached(address _owner, uint256 _epochId) public override returns (uint256) {
        return _getVotePowerOfAt(wNat, _owner, _getEpochInstance(_epochId).votePowerBlock);
    }

    /**
     * @notice Returns current epoch id
     * @dev Should never revert
     */
    function getCurrentEpochId() public view override returns (uint256) {
        return _getEpochId(block.timestamp);
    }

    /**
     * @notice Returns id of the epoch which was opened for price submission at the specified timestamp
     * @param _timestamp            Timestamp as seconds from unix epoch
     * @dev Should never revert
     */
    function getEpochId(uint256 _timestamp) public view override returns (uint256) {
        return _getEpochId(_timestamp);
    }

    /**
     * @notice Reveals submitted price during epoch reveal period
     * @param _voter                Voter address
     * @param _epochId              Id of the epoch in which the price hash was submitted
     * @param _price                Submitted price in USD
     * @notice The hash of _price and _random must be equal to the submitted hash
     * @notice Emits PriceRevealed event
     */
    function _revealPrice(
        address _voter, 
        uint256 _epochId, 
        uint256 _price, 
        uint256 _voterWNatVP
    ) 
        internal
    {
        require(_price < 2**128, ERR_PRICE_TOO_HIGH);
        require(_isEpochRevealInProcess(_epochId), ERR_PRICE_REVEAL_FAILURE);
        // get epoch
        //slither-disable-next-line weak-prng // not used for random
        FtsoEpoch.Instance storage epoch = epochs.instance[_epochId % priceEpochCyclicBufferSize];
        // read all storage from one slot
        bool fallbackMode = epoch.fallbackMode;
        bool initializedForReveal = epoch.initializedForReveal;
        uint256 votePowerBlock = uint256(epoch.votePowerBlock);

        require(initializedForReveal || (fallbackMode && epochs.trustedAddressesMapping[_voter]),
            ERR_EPOCH_NOT_INITIALIZED_FOR_REVEAL);

        // register vote
        (uint256 votePowerNat, uint256 votePowerAsset) = _getVotePowerOf(
            epoch,
            _voter,
            _voterWNatVP,
            fallbackMode,
            votePowerBlock
        );

        epochs._addVote(
            epoch,
            _voter,
            votePowerNat,
            votePowerAsset,
            _price
        );

        // inform about price reveal result
        emit PriceRevealed(_voter, _epochId, _price, block.timestamp, votePowerNat, votePowerAsset);
    }

    /**
     * @notice Returns the list of assets and its vote powers
     * @return _assets              List of assets
     * @return _votePowers          List of vote powers
     * @return _prices              List of asset prices
     */
    function _getAssetData() internal 
        returns (
            IIVPToken[] memory _assets,
            uint256[] memory _votePowers,
            uint256[] memory _prices
        )
    {
        _refreshAssets();
        _assets = assets;

        // compute vote power for each epoch
        _votePowers = new uint256[](_assets.length);
        _prices = new uint256[](_assets.length);
        for (uint256 i = 0; i < _assets.length; i++) {
            _votePowers[i] = _getVotePowerAt(_assets[i], epochs.votePowerBlock);
            (_prices[i], ) = assetFtsos[i].getCurrentPrice();
        }
    }
    
    /**
     * @notice Refreshes epoch state assets if FTSO is in multi-asset mode
     * @dev Assets are determined by other single-asset FTSOs on which the asset may change at any time
     */
    function _refreshAssets() internal {
        if (assetFtsos.length == 1 && assetFtsos[0] == this) {
            return;
        } else {
            for (uint256 i = 0; i < assetFtsos.length; i++) {
                IIVPToken asset = assetFtsos[i].getAsset();
                if (asset == assets[i]) {
                    continue;
                }
                assets[i] = asset;
                if (address(asset) != address(0)) {
                    epochs.assetNorm[asset] = 10**asset.decimals();
                }
            }
        }
    }

    /**
     * @notice Forces finalization of the epoch calculating median price from trusted addresses
     * @param _epochId              Epoch id
     * @param _epoch                Epoch instance
     * @param _exception            Indicates if the exception happened
     * @dev Sets the price to be the median of prices from trusted addresses or force finalize if no votes submitted
     */
    function _medianFinalizePriceEpoch(
        uint256 _epochId,
        FtsoEpoch.Instance storage _epoch,
        bool _exception
    ) 
        internal
    {
        if (_epoch.trustedVotes.length > 0) {
            // finalizationType = PriceFinalizationType.TRUSTED_ADDRESSES
            _epoch.price = FtsoMedian._computeSimple(_epoch.trustedVotes);
            _epoch.finalizationType = _exception ?
                PriceFinalizationType.TRUSTED_ADDRESSES_EXCEPTION : PriceFinalizationType.TRUSTED_ADDRESSES;

            // update price
            assetPriceUSD = uint128(_epoch.price); // no overflow
            assetPriceTimestamp = uint128(block.timestamp); // no overflow
            assetPriceFinalizationType = _epoch.finalizationType;
            lastPriceEpochFinalizationTimestamp = uint240(block.timestamp); // no overflow
            lastPriceEpochFinalizationType = _epoch.finalizationType;
            
            // update trusted providers price
            assetTrustedProvidersPriceUSD = uint128(_epoch.price); // no overflow
            assetTrustedProvidersPriceTimestamp = uint128(block.timestamp); // no overflow
            
            _writeFallbackEpochPriceData(_epochId);

            // inform about epoch result
            emit PriceFinalized(_epochId, _epoch.price, false, 0, 0, _epoch.finalizationType, block.timestamp);
            _epoch.fallbackMode = false; // set back to false for next usage
        } else {
            // finalizationType = PriceFinalizationType.PREVIOUS_PRICE_COPIED
            _forceFinalizePriceEpoch(_epochId, _epoch, _exception);
        }
    }

    /**
     * @notice Forces finalization of the epoch
     * @param _epochId              Epoch id
     * @param _epoch                Epoch instance
     * @param _exception            Indicates if the exception happened
     * @dev Sets the median price to be equal to the price from the previous epoch (if epoch id is 0, price is 0)
     */
    function _forceFinalizePriceEpoch(
        uint256 _epochId,
        FtsoEpoch.Instance storage _epoch,
        bool _exception
    ) 
        internal
    {
        if (_epochId > 0) {
            _epoch.price = assetPriceUSD;
        } else {
            _epoch.price = 0;
        }
        
        _epoch.finalizationType = _exception ? 
            PriceFinalizationType.PREVIOUS_PRICE_COPIED_EXCEPTION : PriceFinalizationType.PREVIOUS_PRICE_COPIED;

        lastPriceEpochFinalizationTimestamp = uint240(block.timestamp); // no overflow
        lastPriceEpochFinalizationType = _epoch.finalizationType;

        _writeFallbackEpochPriceData(_epochId);

        emit PriceFinalized(_epochId, _epoch.price, false, 0, 0, _epoch.finalizationType, block.timestamp);
        _epoch.fallbackMode = false; // set back to false for next usage
    }

    /**
     * @notice Stores epoch data related to price
     * To be implemented in descendants
     */
    function _writeEpochPriceData(
        uint256 /*_epochId*/,
        FtsoMedian.Data memory /*_data*/, 
        uint256[] memory /*_index*/,
        bool /*rewardedFtso*/
    ) 
        internal virtual
    {
        /* empty block */
    }

    /**
     * @notice Stores epoch data related to price (fallback / low turnout / forced mode)
     * To be implemented in descendants
     */
    function _writeFallbackEpochPriceData(uint256 /*_epochId*/) internal virtual {
    }

    /**
     * @notice Returns native token and asset vote power for epoch - returns (0, 0) if in fallback mode
     * @param _epoch                Epoch instance
     * @param _voter                Voter (price provider) address
     * @param _voterWNatVP          Voter nat vote power as queried by price submitter

     * @dev Checks if vote power is sufficient and adjusts vote power if it is too large
     */
    function _getVotePowerOf(
        FtsoEpoch.Instance storage _epoch,
        address _voter,
        uint256 _voterWNatVP,
        bool _fallbackMode,
        uint256 _votePowerBlock
    )
        internal 
        returns (
            uint256 _votePowerNat,
            uint256 _votePowerAsset
        )
    {
        if (_fallbackMode) {
            return (0, 0);
        }

        _votePowerNat = _voterWNatVP;

        _votePowerAsset = _calculateAssetVotePower(_epoch, _voter, _votePowerBlock);

        uint256 maxVotePowerNat = _epoch.maxVotePowerNat;
        uint256 maxVotePowerAsset= _epoch.maxVotePowerAsset;

        if (_votePowerNat > maxVotePowerNat) {
            _votePowerNat = maxVotePowerNat;
        }

        if (_votePowerAsset > maxVotePowerAsset) {
            _votePowerAsset = maxVotePowerAsset;
        }
    }

    /**
     * @notice Returns vote power of the given token at the specified block
     * @param _vp                   Vote power token
     * @param _vpBlock              Vote power block
     * @dev Returns 0 if vote power token is null
     */
    function _getVotePowerAt(IIVPToken _vp, uint256 _vpBlock) internal returns (uint256) {
        if (address(_vp) == address(0)) {
            return 0;
        } else {
            return _vp.totalVotePowerAtCached(_vpBlock);
        }
    }

    /**
     * @notice Returns vote power of the given token at the specified block and for the specified owner
     * @param _vp                   Vote power token
     * @param _owner                Owner address
     * @param _vpBlock              Vote power block
     * @dev Returns 0 if vote power token is null
     */
    function _getVotePowerOfAt(IIVPToken _vp, address _owner, uint256 _vpBlock) internal returns (uint256) {
        if (address(_vp) == address(0)) {
            return 0;
        } else {
            return _vp.votePowerOfAtCached(_owner, _vpBlock);
        }
    }

    function _calculateAssetVotePower(FtsoEpoch.Instance storage _epoch, address _owner, uint256 _votePowerBlock) 
        internal 
        returns (uint256 _votePowerAsset)
    {
        uint256[] memory votePowersAsset = new uint256[](_epoch.assets.length);
        for (uint256 i = 0; i < _epoch.assets.length; i++) {
            votePowersAsset[i] = _getVotePowerOfAt(_epoch.assets[i], _owner, _votePowerBlock);
        }
        _votePowerAsset = epochs._getAssetVotePower(_epoch, votePowersAsset);
    }

    /**
     * @notice Returns random for given epoch id
     */
    function _getRandom(uint256 _epochId) internal view virtual returns (uint256) {
        return uint256(keccak256(abi.encode(priceSubmitter.getRandom(_epochId), address(this))));
    }

    /**
     * @notice Extract vote data from epoch
     * @param _epoch                Epoch instance
     */
    function _readVotes(FtsoEpoch.Instance storage _epoch) internal view 
        returns (
            uint256[] memory _price,
            uint256[] memory _weight,
            uint256[] memory _weightNat
        )
    {
        uint256 length = _epoch.nextVoteIndex;

        _price = new uint256[](length);
        _weightNat = new uint256[](length);

        uint256[] memory weightAsset = new uint256[](length);

        for (uint256 i = 0; i < length; i++) {
            FtsoVote.Instance storage v = _epoch.votes[i];
            _price[i] = v.price;
            _weightNat[i] = v.weightNat;
            weightAsset[i] = v.weightAsset;
        }

        _weight = FtsoEpoch._computeWeights(_epoch, _weightNat, weightAsset);
    }

    /**
     * @notice Extracts reward data for epoch
     * @param _epoch                The epoch instance to read data from
     * @param _data                 Median computation data
     * @param _random               Random number
     * @param _index                Array of vote indices
     * @param _weightNat            Array of native token weights
     */
    function _readRewardData(
        FtsoEpoch.Instance storage _epoch,
        FtsoMedian.Data memory _data,
        uint256 _random,
        uint256[] memory _index, 
        uint256[] memory _weightNat
    ) 
        internal view
        returns (
            address[] memory _eligibleAddresses, 
            uint256[] memory _natWeights,
            uint256 _natWeightsSum
        )
    {
        uint256 voteRewardCount = 0;
        for (uint256 i = _data.quartile1Index; i <= _data.quartile3Index; i++) {
            uint256 idx = _index[i];
            if (_weightNat[idx] > 0) {
                uint128 price = _epoch.votes[idx].price;
                if ((price == _data.quartile1Price || price == _data.quartile3Price) &&
                    ! _isAddressEligible(_random, _epoch.votes[idx].voter)) {
                        continue;
                }
                voteRewardCount++;
            }
        }

        _eligibleAddresses = new address[](voteRewardCount);
        _natWeights = new uint256[](voteRewardCount);
        uint256 cnt = 0;
        for (uint256 i = _data.quartile1Index; i <= _data.quartile3Index; i++) {
            uint256 idx = _index[i];
            uint256 weight = _weightNat[idx];
            if (weight > 0) {
                uint128 price = _epoch.votes[idx].price;
                if ((price == _data.quartile1Price || price == _data.quartile3Price) &&
                    ! _isAddressEligible(_random, _epoch.votes[idx].voter)) {
                    continue;
                }
                _eligibleAddresses[cnt] = _epoch.votes[idx].voter;
                _natWeights[cnt] = weight;
                _natWeightsSum += weight;
                cnt++;
            }
        }
    }

   /**
     * @notice Get epoch instance for given epoch id and check if it can be finished
     * @param _epochId              Epoch id
     * @return _epoch               Return epoch instance
     */
    function _getEpochForFinalization(uint256 _epochId) internal view returns (FtsoEpoch.Instance storage _epoch) {
        require(block.timestamp >= _getEpochRevealEndTime(_epochId), ERR_EPOCH_FINALIZATION_FAILURE);
        _epoch = _getEpochInstance(_epochId);
        require(_epoch.finalizationType == PriceFinalizationType.NOT_FINALIZED, ERR_EPOCH_ALREADY_FINALIZED);
    }

    /**
     * @notice Return epoch instance if epoch id exists in storage, reverts if it is already overwritten
     * @param _epochId              Epoch id
     */
    function _getEpochInstance(uint256 _epochId) internal view returns (FtsoEpoch.Instance storage _epoch) {
        //slither-disable-next-line weak-prng // not used for random
        _epoch = epochs.instance[_epochId % priceEpochCyclicBufferSize];
        require(_epochId == _epoch.epochId, ERR_EPOCH_DATA_NOT_AVAILABLE);
    }

    
    /**
     * @notice Returns the id of the epoch opened for price submission at the given timestamp
     * @param _timestamp            Timestamp as seconds since unix epoch
     * @return Epoch id
     * @dev Should never revert
     */
    function _getEpochId(uint256 _timestamp) internal view returns (uint256) {
        if (_timestamp < firstEpochStartTs) {
            return 0;
        } else {
            return (_timestamp - firstEpochStartTs) / submitPeriodSeconds;
        }
    }

    /**
     * @notice Returns start time of price submission for an epoch instance
     * @param _epochId              Id of epoch instance
     * @return Timestamp as seconds since unix epoch
     */
    function _getEpochSubmitStartTime(uint256 _epochId) internal view returns (uint256) {
        return firstEpochStartTs + _epochId * submitPeriodSeconds;
    }

    /**
     * @notice Returns end time of price submission for an epoch instance = reveal start time
     * @param _epochId              Id of epoch instance
     * @return Timestamp as seconds since unix epoch
     * @dev half-closed interval - end time not included
     */
    function _getEpochSubmitEndTime(uint256 _epochId) internal view returns (uint256) {
        return firstEpochStartTs + (_epochId + 1) * submitPeriodSeconds;
    }

    /**
     * @notice Returns end time of price reveal for an epoch instance
     * @param _epochId              Id of epoch instance
     * @return Timestamp as seconds since unix epoch
     * @dev half-closed interval - end time not included
     */
    function _getEpochRevealEndTime(uint256 _epochId) internal view returns (uint256) {
        return _getEpochSubmitEndTime(_epochId) + revealPeriodSeconds;
    }

    /**
     * @notice Determines if the epoch with the given id is currently in the reveal process
     * @param _epochId              Id of epoch
     * @return True if epoch reveal is in process and false otherwise
     */
    function _isEpochRevealInProcess(uint256 _epochId) internal view returns (bool) {
        uint256 revealStartTime = _getEpochSubmitEndTime(_epochId);
        return revealStartTime <= block.timestamp && block.timestamp < revealStartTime + revealPeriodSeconds;
    }

    /**
     * @notice Checks if an address is eligible for reward (for edge quartile cases)
     * @param _random               Current random for this Ftso
     * @param _address              Address that submitted the price
     * @return _eligible            Return True if the address should be rewarded
     */
    function _isAddressEligible(uint256 _random, address _address) internal pure returns (bool _eligible) {
        _eligible = ((uint256(keccak256(abi.encode(_random, _address))) % 2) == 1);
    }

    function revertNoAccess() internal pure {
        revert(ERR_NO_ACCESS);
    }

    function revertNotActive() internal pure {
        revert(ERR_NOT_ACTIVE);
    }
}
        

./contracts/ftso/interface/IIFtso.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import "../../genesis/interface/IFtsoGenesis.sol";
import "../../userInterfaces/IFtso.sol";
import "../../token/interface/IIVPToken.sol";


interface IIFtso is IFtso, IFtsoGenesis {

    /// function finalizePriceReveal
    /// called by reward manager only on correct timing.
    /// if price reveal period for epoch x ended. finalize.
    /// iterate list of price submissions
    /// find weighted median
    /// find adjucant 50% of price submissions.
    /// Allocate reward for any price submission which is same as a "winning" submission
    function finalizePriceEpoch(uint256 _epochId, bool _returnRewardData) external
        returns(
            address[] memory _eligibleAddresses,
            uint256[] memory _natWeights,
            uint256 _totalNatWeight
        );

    function fallbackFinalizePriceEpoch(uint256 _epochId) external;

    function forceFinalizePriceEpoch(uint256 _epochId) external;

    // activateFtso will be called by ftso manager once ftso is added 
    // before this is done, FTSO can't run
    function activateFtso(
        uint256 _firstEpochStartTs,
        uint256 _submitPeriodSeconds,
        uint256 _revealPeriodSeconds
    ) external;

    function deactivateFtso() external;

    // update initial price and timestamp - only if not active
    function updateInitialPrice(uint256 _initialPriceUSD, uint256 _initialPriceTimestamp) external;

    function configureEpochs(
        uint256 _maxVotePowerNatThresholdFraction,
        uint256 _maxVotePowerAssetThresholdFraction,
        uint256 _lowAssetUSDThreshold,
        uint256 _highAssetUSDThreshold,
        uint256 _highAssetTurnoutThresholdBIPS,
        uint256 _lowNatTurnoutThresholdBIPS,
        address[] memory _trustedAddresses
    ) external;

    function setAsset(IIVPToken _asset) external;

    function setAssetFtsos(IIFtso[] memory _assetFtsos) external;

    // current vote power block will update per reward epoch. 
    // the FTSO doesn't have notion of reward epochs.
    // reward manager only can set this data. 
    function setVotePowerBlock(uint256 _blockNumber) external;

    function initializeCurrentEpochStateForReveal(uint256 _circulatingSupplyNat, bool _fallbackMode) external;
  
    /**
     * @notice Returns ftso manager address
     */
    function ftsoManager() external view returns (address);

    /**
     * @notice Returns the FTSO asset
     * @dev Asset is null in case of multi-asset FTSO
     */
    function getAsset() external view returns (IIVPToken);

    /**
     * @notice Returns the Asset FTSOs
     * @dev AssetFtsos is not null only in case of multi-asset FTSO
     */
    function getAssetFtsos() external view returns (IIFtso[] memory);

    /**
     * @notice Returns current configuration of epoch state
     * @return _maxVotePowerNatThresholdFraction        High threshold for native token vote power per voter
     * @return _maxVotePowerAssetThresholdFraction      High threshold for asset vote power per voter
     * @return _lowAssetUSDThreshold            Threshold for low asset vote power
     * @return _highAssetUSDThreshold           Threshold for high asset vote power
     * @return _highAssetTurnoutThresholdBIPS   Threshold for high asset turnout
     * @return _lowNatTurnoutThresholdBIPS      Threshold for low nat turnout
     * @return _trustedAddresses                Trusted addresses - use their prices if low nat turnout is not achieved
     */
    function epochsConfiguration() external view 
        returns (
            uint256 _maxVotePowerNatThresholdFraction,
            uint256 _maxVotePowerAssetThresholdFraction,
            uint256 _lowAssetUSDThreshold,
            uint256 _highAssetUSDThreshold,
            uint256 _highAssetTurnoutThresholdBIPS,
            uint256 _lowNatTurnoutThresholdBIPS,
            address[] memory _trustedAddresses
        );

    /**
     * @notice Returns parameters necessary for approximately replicating vote weighting.
     * @return _assets                  the list of Assets that are accounted in vote
     * @return _assetMultipliers        weight of each asset in (multiasset) ftso, mutiplied by TERA
     * @return _totalVotePowerNat       total native token vote power at block
     * @return _totalVotePowerAsset     total combined asset vote power at block
     * @return _assetWeightRatio        ratio of combined asset vp vs. native token vp (in BIPS)
     * @return _votePowerBlock          vote powewr block for given epoch
     */
    function getVoteWeightingParameters() external view 
        returns (
            IIVPToken[] memory _assets,
            uint256[] memory _assetMultipliers,
            uint256 _totalVotePowerNat,
            uint256 _totalVotePowerAsset,
            uint256 _assetWeightRatio,
            uint256 _votePowerBlock
        );

    function wNat() external view returns (IIVPToken);
    
    /**
     * @notice Returns current asset price calculated from trusted providers
     * @return _price               Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     * @return _timestamp           Time when price was updated for the last time
     */
    function getCurrentPriceFromTrustedProviders() external view returns (uint256 _price, uint256 _timestamp);
}
          

./contracts/ftso/lib/FtsoEpoch.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;

import "../../token/interface/IIVPToken.sol";
import "../../userInterfaces/IFtso.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../../utils/implementation/SafePct.sol";
import "@openzeppelin/contracts/utils/SafeCast.sol";
import "./FtsoVote.sol";


/**
 * @title A library used for FTSO epoch management
 */
library FtsoEpoch {
    using SafeMath for uint256;
    using SafePct for uint256;
    using SafeCast for uint256;

    struct State {                              // struct holding storage and settings related to epochs
        // storage        
        mapping(uint256 => Instance) instance;  // mapping from epoch id to instance
        mapping(IIVPToken => uint256) assetNorm;  // mapping from asset address to its normalization
        
        // configurable settings
        uint240 votePowerBlock;                 // current block at which the vote power is checked
        uint256 maxVotePowerNatThresholdFraction;       // high threshold for native token vote power per voter
        uint256 maxVotePowerAssetThresholdFraction;     // high threshold for asset vote power per voter
        uint256 lowAssetUSDThreshold;           // threshold for low asset vote power (in scaled USD)
        uint256 highAssetUSDThreshold;          // threshold for high asset vote power (in scaled USD)
        uint256 highAssetTurnoutThresholdBIPS;  // threshold for high asset turnout (in BIPS)
        uint256 lowNatTurnoutThresholdBIPS;     // threshold for low nat turnout (in BIPS)
        address[] trustedAddresses;             // trusted addresses - use their prices if low turnout is not achieved
        mapping(address => bool) trustedAddressesMapping; // for checking addresses in fallback mode
    }

    struct Instance {                           // struct holding epoch votes and results
        uint128 maxVotePowerNat;                // max native token vote power required for voting
        uint128 maxVotePowerAsset;              // max asset vote power required for voting
        uint240 votePowerBlock;                 // block used to obtain vote weights in epoch
        bool initializedForReveal;              // whether epoch instance is initialized for reveal
        bool fallbackMode;                      // current epoch in fallback mode
        uint256 epochId;                        // current epoch id
        uint256 highAssetTurnoutThresholdBIPS;  // threshold for high asset turnout (in BIPS)
        uint256 lowNatTurnoutThresholdBIPS;     // threshold for low nat turnout (in BIPS)
        uint256 circulatingSupplyNat;           // total native token circulating supply at votePowerBlock
        uint256 votePowerNat;                   // total native token vote power at votePowerBlock
        uint256 votePowerAsset;                 // total asset vote power at votePowerBlock
        uint256 accumulatedVotePowerNat;        // total native token vote power accumulated from votes in epoch
        // base weight ratio between asset and native token used to combine weights
        uint256 baseWeightRatio;
        IIVPToken[] assets;                     // list of assets
        uint256[] assetWeightedPrices;          // prices that determine the contributions of assets to vote power
        uint256 nextVoteIndex;
        mapping(uint256 => FtsoVote.Instance) votes; // array of all votes in epoch
        uint256[] trustedVotes;                 // array of all votes from trusted providers in epoch
        uint256 price;                          // consented epoch asset price
        IFtso.PriceFinalizationType finalizationType; // finalization type
    }

    uint256 internal constant BIPS100 = 1e4;                    // 100% in basis points
    uint256 internal constant BIPS50 = BIPS100 / 2;             // 50% in basis points
    uint256 internal constant BIPS45 = (45 * BIPS100) / 100;    // 45% in basis points
    uint256 internal constant BIPS5 = (5 * BIPS100) / 100;      // 5% in basis points
    uint256 internal constant TERA = 10**12;                    // 10^12

    /**
     * @notice Initializes a new epoch instance for reveal with instance specific settings
     * @param _state                    Epoch state
     * @param _instance                 Epoch instance
     * @param _circulatingSupplyNat     Epoch native token circulating supply
     * @param _votePowerNat             Epoch native token vote power
     * @param _assets                   List of assets
     * @param _assetVotePowers          List of asset vote powers
     * @param _assetPrices              List of asset prices
     * @dev _votePowerNat is assumed to be smaller than 2**128 to avoid overflows in computations
     * @dev computed votePowerAsset is assumed to be smaller than 2**128 to avoid overflows in computations
     */
    function _initializeInstanceForReveal(
        State storage _state,
        Instance storage _instance,
        uint256 _circulatingSupplyNat,
        uint256 _votePowerNat,
        IIVPToken[] memory _assets,
        uint256[] memory _assetVotePowers,
        uint256[] memory _assetPrices
    ) 
        internal
    {
        // all divisions guaranteed not to divide with 0 - checked in ftso manager setGovernanceParameters(...)
        _setAssets(_state, _instance, _assets, _assetVotePowers, _assetPrices);

        _instance.highAssetTurnoutThresholdBIPS = _state.highAssetTurnoutThresholdBIPS;
        _instance.lowNatTurnoutThresholdBIPS = _state.lowNatTurnoutThresholdBIPS;
        _instance.circulatingSupplyNat = _circulatingSupplyNat;
        _instance.votePowerNat = _votePowerNat;
        _instance.maxVotePowerNat = (_votePowerNat / _state.maxVotePowerNatThresholdFraction).toUint128();
        _instance.maxVotePowerAsset = 
            (_instance.votePowerAsset / _state.maxVotePowerAssetThresholdFraction).toUint128();
        _instance.initializedForReveal = true;
    }

    /**
     * @notice Adds a vote to the vote array of an epoch instance
     * @param _state                Epoch state
     * @param _instance             Epoch instance
     * @param _voter                Voter address
     * @param _votePowerNat         Vote power for native token
     * @param _votePowerAsset       Vote power for asset
     * @param _price                Price in USD submitted in a vote
     */
    function _addVote(
        State storage _state,
        Instance storage _instance,
        address _voter,
        uint256 _votePowerNat,
        uint256 _votePowerAsset,
        uint256 _price
    )
        internal
    {
        uint256 index = _instance.nextVoteIndex;
        FtsoVote.Instance memory vote = FtsoVote._createInstance(
            _voter,
            _votePowerNat, 
            _votePowerAsset, 
            _instance.votePowerNat, 
            _instance.votePowerAsset,
            _price);

        // cast legal current max voters are ~100 extreme max can be 2000
        vote.index = uint32(index);
        _instance.votes[index] = vote;
        _instance.nextVoteIndex = index + 1;
        _instance.accumulatedVotePowerNat = _instance.accumulatedVotePowerNat.add(_votePowerNat);
        if (_state.trustedAddressesMapping[_voter]) {
            _instance.trustedVotes.push(_price);
        }
    }
    
    /**
     * @notice Sets epoch instance data related to assets
     * @param _state                Epoch state
     * @param _instance             Epoch instance
     * @param _assets               List of assets
     * @param _assetVotePowers      List of asset vote powers
     * @param _assetPrices          List of asset prices
     */
    function _setAssets(
        State storage _state,
        Instance storage _instance,
        IIVPToken[] memory _assets,
        uint256[] memory _assetVotePowers,
        uint256[] memory _assetPrices
    )
        internal
    {
        _instance.assets = _assets;

        _instance.assetWeightedPrices = _getAssetWeightedPrices(_state, _assets, _assetVotePowers, _assetPrices);

        // compute vote power
        uint256 votePower = _getAssetVotePower(_state, _instance, _assetVotePowers);
        _instance.votePowerAsset = votePower;

        // compute base weight ratio between asset and native token
        _instance.baseWeightRatio = _getAssetBaseWeightRatio(_state, votePower);
    }
    
    function _getAssetWeightedPrices(
        State storage _state,
        IIVPToken[] memory _assets,
        uint256[] memory _assetVotePowers,
        uint256[] memory _assetPrices
    )
        internal view
        returns (uint256[] memory)
    {
        uint256 count = _assets.length;
        uint256[] memory values = new uint256[](count); // array of values which eventually contains weighted prices

        // compute sum of vote powers in USD
        uint256 votePowerSumUSD = 0;
        for (uint256 i = 0; i < count; i++) {
            if (address(_assets[i]) == address(0)) {
                continue;
            }
            uint256 votePowerUSD = _assetVotePowers[i].mulDiv(_assetPrices[i], _state.assetNorm[_assets[i]]);
            values[i] = votePowerUSD;
            votePowerSumUSD = votePowerSumUSD.add(votePowerUSD);
        }

        // determine asset weighted prices
        if (votePowerSumUSD > 0) {
            // determine shares based on asset vote powers in USD
            for (uint256 i = 0; i < count; i++) {
                // overriding/reusing array slots
                values[i] = values[i].mulDiv(_assetPrices[i].mul(BIPS100), votePowerSumUSD);
            }
        }
        
        return values;
    }

    /**
     * @notice Returns combined asset vote power
     * @param _state                Epoch state
     * @param _instance             Epoch instance
     * @param _votePowers           Array of asset vote powers
     * @dev Asset vote power is specified in USD and weighted among assets
     */
    function _getAssetVotePower(
        FtsoEpoch.State storage _state,
        FtsoEpoch.Instance storage _instance,
        uint256[] memory _votePowers
    )
        internal view
        returns (uint256)
    {
        uint256 votePower = 0;
        for (uint256 i = 0; i < _instance.assets.length; i++) {
            if (address(_instance.assets[i]) == address(0)) {
                continue;
            }
            votePower = votePower.add(
                _instance.assetWeightedPrices[i].mulDiv(
                    _votePowers[i],
                    _state.assetNorm[_instance.assets[i]]
                ) / BIPS100
            );
        }
        return votePower;
    }

    /**
     * @notice Returns combined asset vote power, same as _getAssetVotePower(...), but doesn't need _instance
     * @param _state                Epoch state
     * @param _assets               The list of assets, some may be address(0)
     * @param _votePowers           Array of asset vote powers
     * @param _assetWeightedPrices  Asset weighted prices as calculated by _getAssetWeightedPrices
     * @dev Asset vote power is specified in USD and weighted among assets
     */
    function _calculateAssetVotePower(
        FtsoEpoch.State storage _state,
        IIVPToken[] memory _assets,
        uint256[] memory _votePowers,
        uint256[] memory _assetWeightedPrices
    )
        internal view
        returns (uint256)
    {
        uint256 votePower = 0;
        for (uint256 i = 0; i < _assets.length; i++) {
            if (address(_assets[i]) != address(0)) {
                uint256 vp = _assetWeightedPrices[i].mulDiv(_votePowers[i], _state.assetNorm[_assets[i]]) / BIPS100;
                votePower = votePower.add(vp);
            }
        }
        return votePower;
    }

    /**
     * Get multipliers for converting asset vote powers to asset vote power weights as in
     * FTSO price calculation. Weights are multiplied by (TERA / BIPS100 * 1e18).
     * Used in VoterWhitelister to emulate ftso weight calculation.
     */
    function _getAssetVoteMultipliers(
        FtsoEpoch.State storage _state,
        IIVPToken[] memory _assets,
        uint256[] memory _assetWeightedPrices
    )
        internal view 
        returns (uint256[] memory _assetMultipliers)
    {
        uint256 numAssets = _assets.length;
        _assetMultipliers = new uint256[](numAssets);
        for (uint256 i = 0; i < numAssets; i++) {
            if (address(_assets[i]) != address(0)) {
                uint256 divisor = _state.assetNorm[_assets[i]];
                // Since we divide by `_state.assetNorm[_instance.assets[i]]` we multiply by 1e18 to prevent underflow
                // (we assume that assetNorm is never much bigger than that)
                // The value is only used in VoterWhitelister._getAssetVotePowerWeights, where we divide by 1e18
                _assetMultipliers[i] = _assetWeightedPrices[i].mulDiv(TERA / BIPS100 * 1e18, divisor);
            } else {
                _assetMultipliers[i] = 0;
            }
        }
    }

    /**
     * @notice Computes the base asset weight ratio
     * @param _state                Epoch state
     * @param _assetVotePowerUSD    Price of the asset in USD
     * @return Base weight ratio for asset in BIPS (a number between 0 and BIPS)
     */
    function _getAssetBaseWeightRatio(
        State storage _state,
        uint256 _assetVotePowerUSD
    )
        internal view
        returns (uint256)
    {
        // highAssetUSDThreshold >= lowAssetUSDThreshold - checked in ftso manager
        uint256 ratio;
        if (_assetVotePowerUSD < _state.lowAssetUSDThreshold) {
            // 0 %
            ratio = 0;
        } else if (_assetVotePowerUSD >= _state.highAssetUSDThreshold) {
            // 50 %
            ratio = BIPS50;
        } else {
            // between 5% and 50% (linear function)
            ratio = (BIPS45 * (_assetVotePowerUSD - _state.lowAssetUSDThreshold)) /
                (_state.highAssetUSDThreshold - _state.lowAssetUSDThreshold) + BIPS5;
        }
        return ratio;
    }

    /**
     * @notice Computes the weight ratio between native token and asset weight that specifies a unified vote weight
     * @param _instance             Epoch instance
     * @return Weight ratio for asset in BIPS (a number between 0 and BIPS)
     * @dev Weight ratio for native token is supposed to be (BIPS - weight ratio for asset)
     */
    function _getWeightRatio(
        Instance storage _instance,
        uint256 _weightNatSum,
        uint256 _weightAssetSum
    )
        internal view
        returns (uint256)
    {
        if (_weightAssetSum == 0) {
            return 0;
        } else if (_weightNatSum == 0) {
            return BIPS100;
        }
        
        uint256 turnout = _weightAssetSum.mulDiv(BIPS100, TERA);
        if (turnout >= _instance.highAssetTurnoutThresholdBIPS) {
            return _instance.baseWeightRatio;
        } else {
            return _instance.baseWeightRatio.mulDiv(turnout, _instance.highAssetTurnoutThresholdBIPS);
        }
    }

    /**
     * @notice Computes vote weights in epoch
     * @param _instance             Epoch instance
     * @param _weightsNat           Array of native token weights
     * @param _weightsAsset         Array of asset weights
     * @return _weights             Array of combined weights
     * @dev All weight parameters and variables are in BIPS
     */
    function _computeWeights(
        FtsoEpoch.Instance storage _instance,
        uint256[] memory _weightsNat,
        uint256[] memory _weightsAsset
    )
        internal view
        returns (uint256[] memory _weights)
    {
        uint256 length = _instance.nextVoteIndex;
        _weights = new uint256[](length);

        uint256 weightNatSum = _arraySum(_weightsNat);
        uint256 weightAssetSum = _arraySum(_weightsAsset);

        // set weight distribution according to weight sums and weight ratio
        uint256 weightNatShare = 0;
        uint256 weightAssetShare = _getWeightRatio(_instance, weightNatSum, weightAssetSum);
        if (weightNatSum > 0) {
            weightNatShare = BIPS100 - weightAssetShare;
        }

        for (uint256 i = 0; i < length; i++) {
            uint256 weightNat = 0;
            if (weightNatShare > 0) {
                weightNat = weightNatShare.mulDiv(TERA * _weightsNat[i], weightNatSum * BIPS100);
            }
            uint256 weightAsset = 0;
            if (weightAssetShare > 0) {
                weightAsset = weightAssetShare.mulDiv(TERA * _weightsAsset[i], weightAssetSum * BIPS100);
            }
            _weights[i] = weightNat + weightAsset;
        }
    }
    
    /**
     * @notice Computes price deviation from the previous epoch in BIPS
     * @param _state                Epoch state
     * @param _epochId              Epoch id
     * @param _epochPrice           Epoch price
     */
    function _getPriceDeviation(
        State storage _state,
        uint256 _epochId,
        uint256 _epochPrice,
        uint256 _priceEpochCyclicBufferSize
    )
        internal view
        returns (uint256)
    {
        if (_epochId == 0) {
            return 0;
        }
        uint256 previousEpochPrice = 
            _state.instance[(_epochId - 1) % _priceEpochCyclicBufferSize].price;
        if (_epochPrice == previousEpochPrice) {
            return 0;
        }
        if (_epochPrice == 0) {
            return TERA; // "infinity"
        }        
        uint256 priceEpochDiff;
        if (previousEpochPrice > _epochPrice) {
            priceEpochDiff = previousEpochPrice - _epochPrice;
        } else {
            priceEpochDiff = _epochPrice - previousEpochPrice;
        }
        return priceEpochDiff.mulDiv(BIPS100, _epochPrice);
    }

    function _findVoteOf(Instance storage _epoch, address _voter) internal view returns (uint256) {
        uint256 length = _epoch.nextVoteIndex;
        for (uint256 i = 0; i < length; i++) {
            if (_epoch.votes[i].voter == _voter) {
                return i + 1;
            }
        }
        return 0;
    }

    /**
     * Calculate sum of all values in an array.
     */
    function _arraySum(uint256[] memory array) private pure returns (uint256) {
        uint256 result = 0;
        for (uint256 i = 0; i < array.length; i++) {
            result = result.add(array[i]);
        }
        return result;
    }
}
          

./contracts/ftso/lib/FtsoMedian.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;


library FtsoMedian {

    struct Data {                   // used for storing the results of weighted median calculation
        uint256 medianIndex;        // index of the median price
        uint256 quartile1Index;     // index of the first price corresponding to the first quartile price
        uint256 quartile3Index;     // index of the last price corresponding to the third quartil price        
        uint256 leftSum;            // auxiliary sum of weights left from the median price
        uint256 rightSum;           // auxiliary sum of weights right from the median price
        uint256 medianWeight;       // weight of the median price
        uint256 lowWeightSum;       // sum of weights corresponding to the prices too low for reward
        uint256 rewardedWeightSum;  // sum of weights corresponding to the prices eligible for reward
        uint256 highWeightSum;      // sum of weights corresponding to the prices too high for reward
        uint256 finalMedianPrice;   // median price
        uint256 quartile1Price;     // first quartile price
        uint256 quartile3Price;     // third quartile price
    }

    struct QSVariables {            // used for storing variables in quick select algorithm
        uint256 leftSum;            // sum of values left to the current position
        uint256 rightSum;           // sum of values right to the current position
        uint256 newLeftSum;         // updated sum of values left to the current position
        uint256 newRightSum;        // updated sum of values right to the current position
        uint256 pivotWeight;        // weight associated with the pivot index
        uint256 leftMedianWeight;   // sum of weights left to the median
        uint256 rightMedianWeight;  // sum of weights right to the median
    }

    struct QSPositions {            // used for storing positions in quick select algorithm
        uint256 pos;                // position index
        uint256 left;               // index left to the position index
        uint256 right;              // index right to the position index
        uint256 pivotId;            // pivot index
    }

    /**
     * @notice Computes the weighted median price and accompanying data
     * @param _price                positional array of prices
     * @param _weight               positional array of weights
     * @return _index               permutation of indices of the input arrays that determines the sorting of _price
     * @return _d                   struct storing the weighted median price and accompanying data
     */
    function _computeWeighted(
        uint256[] memory _price,
        uint256[] memory _weight
    ) 
        internal view 
        returns (
            uint256[] memory _index,
            Data memory _d
        )
    {
        uint256 count = _price.length;

        // initial index state
        _index = new uint256[](count);
        for (uint256 i = 0; i < count; i++) {
            _index[i] = i;
        }

        // quick select algorithm to find the weighted median
        (_d.medianIndex, _d.leftSum, _d.rightSum) = _quickSelect(
            2,
            0,
            count - 1,
            0,
            0,
            _index,
            _price,
            _weight
        );
        _d.medianWeight = _weight[_index[_d.medianIndex]];
        uint256 totalSum = _d.medianWeight + _d.leftSum + _d.rightSum;

        // procedure to find the first quartile bound
        if (_d.medianIndex == 0) {
            // first quartile index is 0
            (_d.quartile1Index, _d.lowWeightSum, ) = (_d.medianIndex, 0, _d.rightSum);
        } else if (_d.leftSum <= totalSum / 4) { 
            // left sum for median is below the first quartile threshold
            (_d.quartile1Index, _d.lowWeightSum, ) = (_d.medianIndex, _d.leftSum, _d.rightSum);
        } else {
            // quick select algorithm to find the first quartile bound (without moving the median index)
            (_d.quartile1Index, _d.lowWeightSum, ) = _quickSelect(
                1,
                0,
                _d.medianIndex - 1,
                0,
                _d.rightSum + _d.medianWeight,
                _index,
                _price,
                _weight
            );
        }

        // procedure to find the third quartile bound
        if (_d.medianIndex == count - 1) {
            // third quartile index is count - 1
            (_d.quartile3Index, , _d.highWeightSum) = (_d.medianIndex, _d.leftSum, 0);
        } else if (_d.rightSum <= totalSum / 4) {
            // right sum for median is below the third quartile threshold
            (_d.quartile3Index, , _d.highWeightSum) = (_d.medianIndex, _d.leftSum, _d.rightSum);
        } else {
            // quick select algorithm to find the third quartile bound (without moving the median index)
            (_d.quartile3Index, , _d.highWeightSum) = _quickSelect(
                3,
                _d.medianIndex + 1,
                count - 1,
                _d.leftSum + _d.medianWeight,
                0,
                _index,
                _price,
                _weight
            );
        }

        // final median price computation
        _d.finalMedianPrice = _price[_index[_d.medianIndex]];
        if (_d.leftSum + _d.medianWeight == totalSum / 2 && totalSum % 2 == 0) {
            // if median is "in the middle", take the average price of the two consecutive prices
            _d.finalMedianPrice =
                (_d.finalMedianPrice + _closestPriceFix(_d.medianIndex, count - 1, _index, _price)) / 2;
        }

        // calculation of first and third quartile index to include indices with the same price
        (_d.quartile1Index, _d.lowWeightSum) = _samePriceFix(
            _d.quartile1Index, 0, -1, _d.lowWeightSum, _index, _price, _weight);
        (_d.quartile3Index, _d.highWeightSum) = _samePriceFix(
            _d.quartile3Index, count - 1, 1, _d.highWeightSum, _index, _price, _weight);

        // store the first and third quartile prices
        _d.quartile1Price = _price[_index[_d.quartile1Index]];
        _d.quartile3Price = _price[_index[_d.quartile3Index]];

        // reward weight sum
        _d.rewardedWeightSum = totalSum - _d.lowWeightSum - _d.highWeightSum;
    }

    /**
     * @notice Performs quick select algorithm
     */
    function _quickSelect(
        uint256 _k,
        uint256 _start,
        uint256 _end,
        uint256 _leftSumInit,
        uint256 _rightSumInit,
        uint256[] memory _index,
        uint256[] memory _price, 
        uint256[] memory _weight
     )
        internal view returns (uint256, uint256, uint256)
     {
        if (_start == _end) {
            return (_start, _leftSumInit, _rightSumInit);
        }
        QSVariables memory s;
        s.leftSum = _leftSumInit;
        s.rightSum = _rightSumInit;
        QSPositions memory p;
        p.left = _start;
        p.right = _end;
        uint256 random = uint256(keccak256(abi.encode(block.difficulty, block.timestamp)));
        uint256 totalSum; 
        while (true) {
            // guarantee: pos is in [left,right] and newLeftSum >= leftSum, newRightSum >= rightSum !!!
            //slither-disable-next-line weak-prng       // no need for secure random, at worst more gas used
            (p.pos, s.newLeftSum, s.newRightSum) = _partition(
                p.left,
                p.right,
                (random % (p.right - p.left + 1)) + p.left, // pivot randomization
                s.leftSum,
                s.rightSum,
                _index,
                _price,
                _weight
            );
            
            p.pivotId = _index[p.pos];
            s.pivotWeight = _weight[p.pivotId];
            totalSum = s.pivotWeight + s.newLeftSum + s.newRightSum;
            if (_k == 2) {
                // last element of s.leftMedianWeight is the real median
                s.leftMedianWeight = totalSum / 2 + (totalSum % 2);  
                s.rightMedianWeight = totalSum - s.leftMedianWeight; 
                // if newSumLeft is contains the median weight!
                if (s.newLeftSum >= s.leftMedianWeight && s.leftMedianWeight > _leftSumInit) { 
                    p.right = p.pos - 1;
                    s.rightSum = s.pivotWeight + s.newRightSum;
                } else if (s.newRightSum > s.rightMedianWeight && s.rightMedianWeight > _rightSumInit) {
                    p.left = p.pos + 1;
                    s.leftSum = s.pivotWeight + s.newLeftSum;
                } else {
                    return (p.pos, s.newLeftSum, s.newRightSum);
                }
            } else if (_k == 1) {
                s.leftMedianWeight = totalSum / 4;
                // rightMedianWeight contains the correct first weight
                s.rightMedianWeight = totalSum - s.leftMedianWeight;
                if (s.newLeftSum > s.leftMedianWeight && s.leftMedianWeight > _leftSumInit) { 
                    p.right = p.pos - 1;
                    s.rightSum = s.pivotWeight + s.newRightSum;
                } else if (s.newRightSum >= s.rightMedianWeight && s.rightMedianWeight > _rightSumInit) {
                    p.left = p.pos + 1;
                    s.leftSum = s.pivotWeight + s.newLeftSum;
                } else {
                    return (p.pos, s.newLeftSum, s.newRightSum);
                }
            } else { // k = 3 - outward bias due to division
                s.rightMedianWeight = totalSum / 4;
                // leftMedianWeight contains the correct last weight
                s.leftMedianWeight = totalSum - s.rightMedianWeight;
                if (s.newLeftSum >= s.leftMedianWeight && s.leftMedianWeight > _leftSumInit) { 
                    p.right = p.pos - 1;
                    s.rightSum = s.pivotWeight + s.newRightSum;
                } else if (s.newRightSum > s.rightMedianWeight && s.rightMedianWeight > _rightSumInit) {
                    p.left = p.pos + 1;
                    s.leftSum = s.pivotWeight + s.newLeftSum;
                } else {
                    return (p.pos, s.newLeftSum, s.newRightSum);
                }
            }
        }

        // should never happen
        assert(false);
        return (0, 0, 0);
    }

    /**
     * @notice Partitions the index array `index` according to the pivot
     */
    function _partition(
        uint256 left0,
        uint256 right0,
        uint256 pivotId,
        uint256 leftSum0, 
        uint256 rightSum0,
        uint256[] memory index,
        uint256[] memory price, 
        uint256[] memory weight
    )
        internal pure returns (uint256, uint256, uint256)
    {
        uint256 pivotValue = price[index[pivotId]];
        uint256[] memory sums = new uint256[](2);
        sums[0] = leftSum0;
        sums[1] = rightSum0;
        uint256 left = left0;
        uint256 right = right0;
        _swap(pivotId, right, index);
        uint256 storeIndex = left;
        for (uint256 i = left; i < right; i++) {
            uint256 eltId = index[i];
            if (price[eltId] < pivotValue) {
                sums[0] += weight[eltId];
                // move index to the left
                _swap(storeIndex, i, index);
                storeIndex++;
            } else {
                sums[1] += weight[eltId];
            }
        }
        _swap(right, storeIndex, index);
        return (storeIndex, sums[0], sums[1]);
    }

    /**
     * @notice Swaps indices `_i` and `_j` in the index array `_index` 
     */
    function _swap(uint256 _i, uint256 _j, uint256[] memory _index) internal pure {
        if (_i == _j) return;
        (_index[_i], _index[_j]) = (_index[_j], _index[_i]);
    }

    /**
     * @notice Handles the same price at the first or third quartile index
     */
    function _samePriceFix(
        uint256 _start,
        uint256 _end,
        int256 _direction,
        uint256 _sumInit,
        uint256[] memory _index,
        uint256[] memory _price,
        uint256[] memory _weight
    )
        internal pure returns (uint256, uint256)
    {
        uint256 weightSum = _sumInit;
        if ((int256(_start) - int256(_end)) * _direction >= 0) return (_start, _sumInit);
        uint256 thePrice = _price[_index[_start]];
        int256 storeIndex = int256(_start) + _direction;
        uint256 eltId;
        for (int256 i = int256(_start) + _direction; (i - int256(_end)) * _direction <= 0; i += _direction) {
            eltId = _index[uint256(i)];
            if (_price[eltId] == thePrice) {
                weightSum -= _weight[eltId];
                _swap(uint256(storeIndex), uint256(i), _index);
                storeIndex += _direction;
            }
        }
        return (uint256(storeIndex - _direction), weightSum);
    }

    /**
     * @notice Finds the price between `_start + 1` and `_end`that is the closest to the price at `_start` index
     * @dev If _start = _end, _price[_start] is returned
     */
    function _closestPriceFix(
        uint256 _start,
        uint256 _end,
        uint256[] memory _index,
        uint256[] memory _price
    )
        internal pure returns (uint256)
    {
        if (_start == _end) {
            // special case
            return _price[_index[_start]];
        }

        // find the closest price between `_start + 1` and `_end`
        uint256 closestPrice = _price[_index[_start + 1]];
        uint256 newPrice;
        for (uint256 i = _start + 2; i <= _end; i++) {
            newPrice = _price[_index[i]];
            // assumes all the elements to the right of start are greater or equal 
            if (newPrice < closestPrice) {
                closestPrice = newPrice;
            }
        }
        return closestPrice;
    }

    
    /**
     * @notice Computes the simple median price (using insertion sort) - sorts original array
     * @param _prices               positional array of prices to be sorted
     * @return _finalMedianPrice    median price
     */
    function _computeSimple(
        uint256[] memory _prices
    ) 
        internal pure 
        returns (
            uint256 _finalMedianPrice
        )
    {
        uint256 length = _prices.length;
        assert(length > 0);

        for (uint256 i = 1; i < length; i++) {
            // price to sort next
            uint256 currentPrice = _prices[i];

            // shift bigger prices right
            uint256 j = i;
            while (j > 0 && _prices[j - 1] > currentPrice) {
                _prices[j] = _prices[j - 1];
                j--; // no underflow
            }
            // insert 
            _prices[j] = currentPrice;
        }

        uint256 middleIndex = length / 2;
        if (length % 2 == 1) {
            return _prices[middleIndex];
        } else {
            // if median is "in the middle", take the average price of the two consecutive prices
            return (_prices[middleIndex - 1] + _prices[middleIndex]) / 2;
        }
    }
}
          

./contracts/ftso/lib/FtsoVote.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;

import "../../utils/implementation/SafePct.sol";


/**
 * @title A library used for FTSO vote management
 * @dev Every vote corresponds to a specific FTSO epoch
 */
library FtsoVote {

    using SafePct for uint256;

    struct Instance {                           // struct holding vote data
        uint128 price;                          // submitted price in USD
        uint64 weightNat;                       // native token weight
        uint64 weightAsset;                     // asset weight
        address voter;                          // the sender of this vote
        uint32 index;
    }

    uint256 internal constant TERA = 10**12;    // 10^12

    /**
     * @notice Creates a vote instance and stores data associated with the vote
     * @param _voter                Sender of the vote
     * @param _votePowerNat         Native token vote power 
     * @param _votePowerAsset       Asset vote power
     * @param _totalVotePowerNat    Total native token vote power in epoch
     * @param _totalVotePowerAsset  Total asset vote power in epoch
     * @param _price                Price in USD submitted in a vote
     * @return vote                 The combined vote
     */
    function _createInstance(
        address _voter,
        uint256 _votePowerNat,
        uint256 _votePowerAsset,
        uint256 _totalVotePowerNat,
        uint256 _totalVotePowerAsset,
        uint256 _price
    ) 
        internal pure 
        returns (Instance memory vote)
    {
        vote.voter = _voter;
        vote.weightNat = _getWeight(_votePowerNat, _totalVotePowerNat);
        vote.weightAsset = _getWeight(_votePowerAsset, _totalVotePowerAsset);
        vote.price = uint128(_price);
    }

    /**
     * @notice Returns the vote weight (NAT or asset) computed based on vote power
     * @param _votePower            Vote power
     * @param _totalVotePower       Total vote power in epoch
     * @return Vote weight
     * @dev Vote power is adjusted to uint64 and is a number between 0 and TERA
     */
    function _getWeight(uint256 _votePower, uint256 _totalVotePower) private pure returns (uint64) {
        if (_totalVotePower == 0 || _votePower == 0) {
            return 0;
        } else {
            return uint64(_votePower.mulDiv(TERA, _totalVotePower));
        }
    }
}
          

./contracts/genesis/interface/IFtsoGenesis.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;


interface IFtsoGenesis {

    /**
     * @notice Reveals submitted price during epoch reveal period - only price submitter
     * @param _voter                Voter address
     * @param _epochId              Id of the epoch in which the price hash was submitted
     * @param _price                Submitted price in USD
     * @notice The hash of _price and _random must be equal to the submitted hash
     * @notice Emits PriceRevealed event
     */
    function revealPriceSubmitter(
        address _voter,
        uint256 _epochId,
        uint256 _price,
        uint256 _wNatVP
    ) external;

    /**
     * @notice Get (and cache) wNat vote power for specified voter and given epoch id
     * @param _voter                Voter address
     * @param _epochId              Id of the epoch in which the price hash was submitted
     * @return wNat vote power
     */
    function wNatVotePowerCached(address _voter, uint256 _epochId) external returns (uint256);
}
          

./contracts/genesis/interface/IFtsoManagerGenesis.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;


interface IFtsoManagerGenesis {

    function getCurrentPriceEpochId() external view returns (uint256 _priceEpochId);

}
          

./contracts/genesis/interface/IFtsoRegistryGenesis.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import "./IFtsoGenesis.sol";


interface IFtsoRegistryGenesis {

    function getFtsos(uint256[] memory _indices) external view returns(IFtsoGenesis[] memory _ftsos);
}
          

./contracts/token/interface/IICleanable.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

interface IICleanable {
    /**
     * Set the contract that is allowed to call history cleaning methods.
     */
    function setCleanerContract(address _cleanerContract) external;
    
    /**
     * Set the cleanup block number.
     * Historic data for the blocks before `cleanupBlockNumber` can be erased,
     * history before that block should never be used since it can be inconsistent.
     * In particular, cleanup block number must be before current vote power block.
     * @param _blockNumber The new cleanup block number.
     */
    function setCleanupBlockNumber(uint256 _blockNumber) external;
    
    /**
     * Get the current cleanup block number.
     */
    function cleanupBlockNumber() external view returns (uint256);
}
          

./contracts/token/interface/IIGovernanceVotePower.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import "../../userInterfaces/IVPToken.sol";
import "../../userInterfaces/IGovernanceVotePower.sol";

interface IIGovernanceVotePower is IGovernanceVotePower {
    /**
     * Event triggered when an delegator's balance changes.
     *
     * Note: the event is always emitted from `GovernanceVotePower`.
     */
    event DelegateVotesChanged(
    address indexed delegate, 
    uint256 previousBalance, 
    uint256 newBalance
    );

    /**
     * Event triggered when an account delegates to another account.
     *
     * Note: the event is always emitted from `GovernanceVotePower`.
     */
    event DelegateChanged(
    address indexed delegator, 
    address indexed fromDelegate, 
    address indexed toDelegate
    );

    /**
     * Update vote powers when tokens are transfered.
     **/
    function updateAtTokenTransfer(
        address _from,
        address _to,
        uint256 _fromBalance,
        uint256 _toBalance,
        uint256 _amount
    ) external;

    /**
     * Set the cleanup block number.
     * Historic data for the blocks before `cleanupBlockNumber` can be erased,
     * history before that block should never be used since it can be inconsistent.
     * In particular, cleanup block number must be before current vote power block.
     * @param _blockNumber The new cleanup block number.
     */
    function setCleanupBlockNumber(uint256 _blockNumber) external;

    /**
     * Set the contract that is allowed to call history cleaning methods.
     */
    function setCleanerContract(address _cleanerContract) external;

    /**
     * @notice Get the token that this governance vote power contract belongs to.
     */
    function ownerToken() external view returns (IVPToken);

    function getCleanupBlockNumber() external view returns(uint256);
}
          

./contracts/token/interface/IIVPContract.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import "../../userInterfaces/IVPToken.sol";
import "../../userInterfaces/IVPContractEvents.sol";
import "./IICleanable.sol";

interface IIVPContract is IICleanable, IVPContractEvents {
    /**
     * Update vote powers when tokens are transfered.
     * Also update delegated vote powers for percentage delegation
     * and check for enough funds for explicit delegations.
     **/
    function updateAtTokenTransfer(
        address _from, 
        address _to, 
        uint256 _fromBalance,
        uint256 _toBalance,
        uint256 _amount
    ) external;

    /**
     * @notice Delegate `_bips` percentage of voting power to `_to` from `_from`
     * @param _from The address of the delegator
     * @param _to The address of the recipient
     * @param _balance The delegator's current balance
     * @param _bips The percentage of voting power to be delegated expressed in basis points (1/100 of one percent).
     *   Not cummulative - every call resets the delegation value (and value of 0 revokes delegation).
     **/
    function delegate(
        address _from, 
        address _to, 
        uint256 _balance, 
        uint256 _bips
    ) external;
    
    /**
     * @notice Explicitly delegate `_amount` of voting power to `_to` from `msg.sender`.
     * @param _from The address of the delegator
     * @param _to The address of the recipient
     * @param _balance The delegator's current balance
     * @param _amount An explicit vote power amount to be delegated.
     *   Not cummulative - every call resets the delegation value (and value of 0 undelegates `to`).
     **/    
    function delegateExplicit(
        address _from, 
        address _to, 
        uint256 _balance, 
        uint _amount
    ) external;    

    /**
     * @notice Revoke all delegation from sender to `_who` at given block. 
     *    Only affects the reads via `votePowerOfAtCached()` in the block `_blockNumber`.
     *    Block `_blockNumber` must be in the past. 
     *    This method should be used only to prevent rogue delegate voting in the current voting block.
     *    To stop delegating use delegate/delegateExplicit with value of 0 or undelegateAll/undelegateAllExplicit.
     * @param _from The address of the delegator
     * @param _who Address of the delegatee
     * @param _balance The delegator's current balance
     * @param _blockNumber The block number at which to revoke delegation.
     **/
    function revokeDelegationAt(
        address _from, 
        address _who, 
        uint256 _balance,
        uint _blockNumber
    ) external;
    
        /**
     * @notice Undelegate all voting power for delegates of `msg.sender`
     *    Can only be used with percentage delegation.
     *    Does not reset delegation mode back to NOTSET.
     * @param _from The address of the delegator
     **/
    function undelegateAll(
        address _from,
        uint256 _balance
    ) external;
    
    /**
     * @notice Undelegate all explicit vote power by amount delegates for `msg.sender`.
     *    Can only be used with explicit delegation.
     *    Does not reset delegation mode back to NOTSET.
     * @param _from The address of the delegator
     * @param _delegateAddresses Explicit delegation does not store delegatees' addresses, 
     *   so the caller must supply them.
     * @return The amount still delegated (in case the list of delegates was incomplete).
     */
    function undelegateAllExplicit(
        address _from, 
        address[] memory _delegateAddresses
    ) external returns (uint256);
    
    /**
    * @notice Get the vote power of `_who` at block `_blockNumber`
    *   Reads/updates cache and upholds revocations.
    * @param _who The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return Vote power of `_who` at `_blockNumber`.
    */
    function votePowerOfAtCached(address _who, uint256 _blockNumber) external returns(uint256);
    
    /**
     * @notice Get the current vote power of `_who`.
     * @param _who The address to get voting power.
     * @return Current vote power of `_who`.
     */
    function votePowerOf(address _who) external view returns(uint256);
    
    /**
    * @notice Get the vote power of `_who` at block `_blockNumber`
    * @param _who The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return Vote power of `_who` at `_blockNumber`.
    */
    function votePowerOfAt(address _who, uint256 _blockNumber) external view returns(uint256);

    /**
    * @notice Get the vote power of `_who` at block `_blockNumber`, ignoring revocation information (and cache).
    * @param _who The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return Vote power of `_who` at `_blockNumber`. Result doesn't change if vote power is revoked.
    */
    function votePowerOfAtIgnoringRevocation(address _who, uint256 _blockNumber) external view returns(uint256);

    /**
     * Return vote powers for several addresses in a batch.
     * @param _owners The list of addresses to fetch vote power of.
     * @param _blockNumber The block number at which to fetch.
     * @return A list of vote powers.
     */    
    function batchVotePowerOfAt(
        address[] memory _owners, 
        uint256 _blockNumber
    )
        external view returns(uint256[] memory);

    /**
    * @notice Get current delegated vote power `_from` delegator delegated `_to` delegatee.
    * @param _from Address of delegator
    * @param _to Address of delegatee
    * @param _balance The delegator's current balance
    * @return The delegated vote power.
    */
    function votePowerFromTo(
        address _from, 
        address _to, 
        uint256 _balance
    ) external view returns(uint256);
    
    /**
    * @notice Get delegated the vote power `_from` delegator delegated `_to` delegatee at `_blockNumber`.
    * @param _from Address of delegator
    * @param _to Address of delegatee
    * @param _balance The delegator's current balance
    * @param _blockNumber The block number at which to fetch.
    * @return The delegated vote power.
    */
    function votePowerFromToAt(
        address _from, 
        address _to, 
        uint256 _balance,
        uint _blockNumber
    ) external view returns(uint256);

    /**
     * @notice Compute the current undelegated vote power of `_owner`
     * @param _owner The address to get undelegated voting power.
     * @param _balance Owner's current balance
     * @return The unallocated vote power of `_owner`
     */
    function undelegatedVotePowerOf(
        address _owner,
        uint256 _balance
    ) external view returns(uint256);

    /**
     * @notice Get the undelegated vote power of `_owner` at given block.
     * @param _owner The address to get undelegated voting power.
     * @param _blockNumber The block number at which to fetch.
     * @return The undelegated vote power of `_owner` (= owner's own balance minus all delegations from owner)
     */
    function undelegatedVotePowerOfAt(
        address _owner, 
        uint256 _balance,
        uint256 _blockNumber
    ) external view returns(uint256);

    /**
     * @notice Get the delegation mode for '_who'. This mode determines whether vote power is
     *  allocated by percentage or by explicit value.
     * @param _who The address to get delegation mode.
     * @return Delegation mode (NOTSET=0, PERCENTAGE=1, AMOUNT=2))
     */
    function delegationModeOf(address _who) external view returns (uint256);
    
    /**
    * @notice Get the vote power delegation `_delegateAddresses` 
    *  and `pcts` of an `_owner`. Returned in two separate positional arrays.
    * @param _owner The address to get delegations.
    * @return _delegateAddresses Positional array of delegation addresses.
    * @return _bips Positional array of delegation percents specified in basis points (1/100 or 1 percent)
    * @return _count The number of delegates.
    * @return _delegationMode The mode of the delegation (NOTSET=0, PERCENTAGE=1, AMOUNT=2).
    */
    function delegatesOf(
        address _owner
    )
        external view 
        returns (
            address[] memory _delegateAddresses, 
            uint256[] memory _bips,
            uint256 _count,
            uint256 _delegationMode
        );

    /**
    * @notice Get the vote power delegation `delegationAddresses` 
    *  and `pcts` of an `_owner`. Returned in two separate positional arrays.
    * @param _owner The address to get delegations.
    * @param _blockNumber The block for which we want to know the delegations.
    * @return _delegateAddresses Positional array of delegation addresses.
    * @return _bips Positional array of delegation percents specified in basis points (1/100 or 1 percent)
    * @return _count The number of delegates.
    * @return _delegationMode The mode of the delegation (NOTSET=0, PERCENTAGE=1, AMOUNT=2).
    */
    function delegatesOfAt(
        address _owner,
        uint256 _blockNumber
    )
        external view 
        returns (
            address[] memory _delegateAddresses, 
            uint256[] memory _bips,
            uint256 _count,
            uint256 _delegationMode
        );

    /**
     * The VPToken (or some other contract) that owns this VPContract.
     * All state changing methods may be called only from this address.
     * This is because original msg.sender is sent in `_from` parameter
     * and we must be sure that it cannot be faked by directly calling VPContract.
     * Owner token is also used in case of replacement to recover vote powers from balances.
     */
    function ownerToken() external view returns (IVPToken);
    
    /**
     * Return true if this IIVPContract is configured to be used as a replacement for other contract.
     * It means that vote powers are not necessarily correct at the initialization, therefore
     * every method that reads vote power must check whether it is initialized for that address and block.
     */
    function isReplacement() external view returns (bool);
}
          

./contracts/token/interface/IIVPToken.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import "../../userInterfaces/IVPToken.sol";
import "../../userInterfaces/IGovernanceVotePower.sol";
import "./IIVPContract.sol";
import "./IIGovernanceVotePower.sol";
import "./IICleanable.sol";

interface IIVPToken is IVPToken, IICleanable {
    /**
     * Set the contract that is allowed to set cleanupBlockNumber.
     * Usually this will be an instance of CleanupBlockNumberManager.
     */
    function setCleanupBlockNumberManager(address _cleanupBlockNumberManager) external;
    
    /**
     * Sets new governance vote power contract that allows token owners to participate in governance voting
     * and delegate governance vote power. 
     */
    function setGovernanceVotePower(IIGovernanceVotePower _governanceVotePower) external;
    
    /**
    * @notice Get the total vote power at block `_blockNumber` using cache.
    *   It tries to read the cached value and if not found, reads the actual value and stores it in cache.
    *   Can only be used if `_blockNumber` is in the past, otherwise reverts.    
    * @param _blockNumber The block number at which to fetch.
    * @return The total vote power at the block (sum of all accounts' vote powers).
    */
    function totalVotePowerAtCached(uint256 _blockNumber) external returns(uint256);
    
    /**
    * @notice Get the vote power of `_owner` at block `_blockNumber` using cache.
    *   It tries to read the cached value and if not found, reads the actual value and stores it in cache.
    *   Can only be used if _blockNumber is in the past, otherwise reverts.    
    * @param _owner The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return Vote power of `_owner` at `_blockNumber`.
    */
    function votePowerOfAtCached(address _owner, uint256 _blockNumber) external returns(uint256);

    /**
     * Return vote powers for several addresses in a batch.
     * @param _owners The list of addresses to fetch vote power of.
     * @param _blockNumber The block number at which to fetch.
     * @return A list of vote powers.
     */    
    function batchVotePowerOfAt(
        address[] memory _owners, 
        uint256 _blockNumber
    ) external view returns(uint256[] memory);
}
          

./contracts/userInterfaces/IFtso.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

interface IFtso {
    enum PriceFinalizationType {
        // initial state
        NOT_FINALIZED,
        // median calculation used to find price
        WEIGHTED_MEDIAN,
        // low turnout - price calculated from median of trusted addresses
        TRUSTED_ADDRESSES,
        // low turnout + no votes from trusted addresses - price copied from previous epoch
        PREVIOUS_PRICE_COPIED,
        // price calculated from median of trusted addresses - triggered due to an exception
        TRUSTED_ADDRESSES_EXCEPTION,
        // previous price copied - triggered due to an exception
        PREVIOUS_PRICE_COPIED_EXCEPTION
    }

    event PriceRevealed(
        address indexed voter, uint256 indexed epochId, uint256 price, uint256 timestamp,
        uint256 votePowerNat, uint256 votePowerAsset
    );

    event PriceFinalized(
        uint256 indexed epochId, uint256 price, bool rewardedFtso,
        uint256 lowRewardPrice, uint256 highRewardPrice, PriceFinalizationType finalizationType,
        uint256 timestamp
    );

    event PriceEpochInitializedOnFtso(
        uint256 indexed epochId, uint256 endTime, uint256 timestamp
    );

    event LowTurnout(
        uint256 indexed epochId,
        uint256 natTurnout,
        uint256 lowNatTurnoutThresholdBIPS,
        uint256 timestamp
    );

    /**
     * @notice Returns if FTSO is active
     */
    function active() external view returns (bool);

    /**
     * @notice Returns the FTSO symbol
     */
    function symbol() external view returns (string memory);

    /**
     * @notice Returns current epoch id
     */
    function getCurrentEpochId() external view returns (uint256);

    /**
     * @notice Returns id of the epoch which was opened for price submission at the specified timestamp
     * @param _timestamp            Timestamp as seconds from unix epoch
     */
    function getEpochId(uint256 _timestamp) external view returns (uint256);
    
    /**
     * @notice Returns random number of the specified epoch
     * @param _epochId              Id of the epoch
     */
    function getRandom(uint256 _epochId) external view returns (uint256);

    /**
     * @notice Returns asset price consented in specific epoch
     * @param _epochId              Id of the epoch
     * @return Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     */
    function getEpochPrice(uint256 _epochId) external view returns (uint256);

    /**
     * @notice Returns current epoch data
     * @return _epochId                 Current epoch id
     * @return _epochSubmitEndTime      End time of the current epoch price submission as seconds from unix epoch
     * @return _epochRevealEndTime      End time of the current epoch price reveal as seconds from unix epoch
     * @return _votePowerBlock          Vote power block for the current epoch
     * @return _fallbackMode            Current epoch in fallback mode - only votes from trusted addresses will be used
     * @dev half-closed intervals - end time not included
     */
    function getPriceEpochData() external view returns (
        uint256 _epochId,
        uint256 _epochSubmitEndTime,
        uint256 _epochRevealEndTime,
        uint256 _votePowerBlock,
        bool _fallbackMode
    );

    /**
     * @notice Returns current epoch data
     * @return _firstEpochStartTs           First epoch start timestamp
     * @return _submitPeriodSeconds         Submit period in seconds
     * @return _revealPeriodSeconds         Reveal period in seconds
     */
    function getPriceEpochConfiguration() external view returns (
        uint256 _firstEpochStartTs,
        uint256 _submitPeriodSeconds,
        uint256 _revealPeriodSeconds
    );
    
    /**
     * @notice Returns asset price submitted by voter in specific epoch
     * @param _epochId              Id of the epoch
     * @param _voter                Address of the voter
     * @return Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     */
    function getEpochPriceForVoter(uint256 _epochId, address _voter) external view returns (uint256);

    /**
     * @notice Returns current asset price
     * @return _price               Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     * @return _timestamp           Time when price was updated for the last time
     */
    function getCurrentPrice() external view returns (uint256 _price, uint256 _timestamp);

    /**
     * @notice Returns current asset price details
     * @return _price                                   Price in USD multiplied by ASSET_PRICE_USD_DECIMALS
     * @return _priceTimestamp                          Time when price was updated for the last time
     * @return _priceFinalizationType                   Finalization type when price was updated for the last time
     * @return _lastPriceEpochFinalizationTimestamp     Time when last price epoch was finalized
     * @return _lastPriceEpochFinalizationType          Finalization type of last finalized price epoch
     */
    function getCurrentPriceDetails() external view returns (
        uint256 _price,
        uint256 _priceTimestamp,
        PriceFinalizationType _priceFinalizationType,
        uint256 _lastPriceEpochFinalizationTimestamp,
        PriceFinalizationType _lastPriceEpochFinalizationType
    );

    /**
     * @notice Returns current random number
     */
    function getCurrentRandom() external view returns (uint256);
}
          

./contracts/userInterfaces/IGovernanceVotePower.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

interface IGovernanceVotePower {
    /**
     * @notice Delegate all governance vote power of `msg.sender` to `_to`.
     * @param _to The address of the recipient
     **/
    function delegate(address _to) external;

    /**
     * @notice Undelegate all governance vote power of `msg.sender``.
     **/
    function undelegate() external;

    /**
    * @notice Get the governance vote power of `_who` at block `_blockNumber`
    * @param _who The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return _votePower    Governance vote power of `_who` at `_blockNumber`.
    */
    function votePowerOfAt(address _who, uint256 _blockNumber) external view returns(uint256);

    /**
    * @notice Get the vote power of `account` at the current block.
    * @param account The address to get voting power.
    * @return Vote power of `account` at the current block number.
    */    
    function getVotes(address account) external view returns (uint256);

    /**
    * @notice Get the delegate's address of `_who` at block `_blockNumber`
    * @param _who The address to get delegate's address.
    * @param _blockNumber The block number at which to fetch.
    * @return Delegate's address of `_who` at `_blockNumber`.
    */
    function getDelegateOfAt(address _who, uint256 _blockNumber) external view returns (address);

    /**
    * @notice Get the delegate's address of `_who` at the current block.
    * @param _who The address to get delegate's address.
    * @return Delegate's address of `_who` at the current block number.
    */    
    function getDelegateOfAtNow(address _who) external  view returns (address);

}
          

./contracts/userInterfaces/IPriceSubmitter.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import "../genesis/interface/IFtsoGenesis.sol";
import "../genesis/interface/IFtsoRegistryGenesis.sol";
import "../genesis/interface/IFtsoManagerGenesis.sol";

interface IPriceSubmitter {
    /**
     * Event emitted when hash was submitted through PriceSubmitter.
     * @param submitter the address of the sender
     * @param epochId current price epoch id
     * @param hash the submitted hash
     * @param timestamp current block timestamp
     */
    event HashSubmitted(
        address indexed submitter,
        uint256 indexed epochId,
        bytes32 hash,
        uint256 timestamp
    );

    /**
     * Event emitted when prices were revealed through PriceSubmitter.
     * @param voter the address of the sender
     * @param epochId id of the epoch in which the price hash was submitted
     * @param ftsos array of ftsos that correspond to the indexes in the call
     * @param prices the submitted prices
     * @param timestamp current block timestamp
     */
    event PricesRevealed(
        address indexed voter,
        uint256 indexed epochId,
        IFtsoGenesis[] ftsos,
        uint256[] prices,
        uint256 random,
        uint256 timestamp
    );
    
    /**
     * @notice Submits hash for current epoch
     * @param _epochId              Target epoch id to which hash is submitted
     * @param _hash                 Hash of ftso indices, prices, random number and voter address
     * @notice Emits HashSubmitted event
     */
    function submitHash(
        uint256 _epochId,
        bytes32 _hash
    ) external;

    /**
     * @notice Reveals submitted prices during epoch reveal period
     * @param _epochId              Id of the epoch in which the price hashes was submitted
     * @param _ftsoIndices          List of increasing ftso indices
     * @param _prices               List of submitted prices in USD
     * @param _random               Submitted random number
     * @notice The hash of ftso indices, prices, random number and voter address must be equal to the submitted hash
     * @notice Emits PricesRevealed event
     */
    function revealPrices(
        uint256 _epochId,
        uint256[] memory _ftsoIndices,
        uint256[] memory _prices,
        uint256 _random
    ) external;

    /**
     * Returns bitmap of all ftso's for which `_voter` is allowed to submit prices/hashes.
     * If voter is allowed to vote for ftso at index (see *_FTSO_INDEX), the corrsponding
     * bit in the result will be 1.
     */    
    function voterWhitelistBitmap(address _voter) external view returns (uint256);

    function getVoterWhitelister() external view returns (address);
    function getFtsoRegistry() external view returns (IFtsoRegistryGenesis);
    function getFtsoManager() external view returns (IFtsoManagerGenesis);

    /**
     * @notice Returns current random number
     */
    function getCurrentRandom() external view returns (uint256);
    
    /**
     * @notice Returns random number of the specified epoch
     * @param _epochId              Id of the epoch
     */
    function getRandom(uint256 _epochId) external view returns (uint256);
}
          

./contracts/userInterfaces/IVPContractEvents.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

interface IVPContractEvents {
    /**
     * Event triggered when an account delegates or undelegates another account. 
     * Definition: `votePowerFromTo(from, to)` is `changed` from `priorVotePower` to `newVotePower`.
     * For undelegation, `newVotePower` is 0.
     *
     * Note: the event is always emitted from VPToken's `writeVotePowerContract`.
     */
    event Delegate(address indexed from, address indexed to, uint256 priorVotePower, uint256 newVotePower);
    
    /**
     * Event triggered only when account `delegator` revokes delegation to `delegatee`
     * for a single block in the past (typically the current vote block).
     *
     * Note: the event is always emitted from VPToken's `writeVotePowerContract` and/or `readVotePowerContract`.
     */
    event Revoke(address indexed delegator, address indexed delegatee, uint256 votePower, uint256 blockNumber);
}
          

./contracts/userInterfaces/IVPToken.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IGovernanceVotePower} from "./IGovernanceVotePower.sol";
import {IVPContractEvents} from "./IVPContractEvents.sol";

interface IVPToken is IERC20 {
    /**
     * @notice Delegate by percentage `_bips` of voting power to `_to` from `msg.sender`.
     * @param _to The address of the recipient
     * @param _bips The percentage of voting power to be delegated expressed in basis points (1/100 of one percent).
     *   Not cummulative - every call resets the delegation value (and value of 0 undelegates `to`).
     **/
    function delegate(address _to, uint256 _bips) external;
    
    /**
     * @notice Undelegate all percentage delegations from teh sender and then delegate corresponding 
     *   `_bips` percentage of voting power from the sender to each member of `_delegatees`.
     * @param _delegatees The addresses of the new recipients.
     * @param _bips The percentages of voting power to be delegated expressed in basis points (1/100 of one percent).
     *   Total of all `_bips` values must be at most 10000.
     **/
    function batchDelegate(address[] memory _delegatees, uint256[] memory _bips) external;
        
    /**
     * @notice Explicitly delegate `_amount` of voting power to `_to` from `msg.sender`.
     * @param _to The address of the recipient
     * @param _amount An explicit vote power amount to be delegated.
     *   Not cummulative - every call resets the delegation value (and value of 0 undelegates `to`).
     **/    
    function delegateExplicit(address _to, uint _amount) external;

    /**
    * @notice Revoke all delegation from sender to `_who` at given block. 
    *    Only affects the reads via `votePowerOfAtCached()` in the block `_blockNumber`.
    *    Block `_blockNumber` must be in the past. 
    *    This method should be used only to prevent rogue delegate voting in the current voting block.
    *    To stop delegating use delegate/delegateExplicit with value of 0 or undelegateAll/undelegateAllExplicit.
    * @param _who Address of the delegatee
    * @param _blockNumber The block number at which to revoke delegation.
    */
    function revokeDelegationAt(address _who, uint _blockNumber) external;
    
    /**
     * @notice Undelegate all voting power for delegates of `msg.sender`
     *    Can only be used with percentage delegation.
     *    Does not reset delegation mode back to NOTSET.
     **/
    function undelegateAll() external;
    
    /**
     * @notice Undelegate all explicit vote power by amount delegates for `msg.sender`.
     *    Can only be used with explicit delegation.
     *    Does not reset delegation mode back to NOTSET.
     * @param _delegateAddresses Explicit delegation does not store delegatees' addresses, 
     *   so the caller must supply them.
     * @return The amount still delegated (in case the list of delegates was incomplete).
     */
    function undelegateAllExplicit(address[] memory _delegateAddresses) external returns (uint256);


    /**
     * @dev Should be compatible with ERC20 method
     */
    function name() external view returns (string memory);

    /**
     * @dev Should be compatible with ERC20 method
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Should be compatible with ERC20 method
     */
    function decimals() external view returns (uint8);
    

    /**
     * @notice Total amount of tokens at a specific `_blockNumber`.
     * @param _blockNumber The block number when the totalSupply is queried
     * @return The total amount of tokens at `_blockNumber`
     **/
    function totalSupplyAt(uint _blockNumber) external view returns(uint256);

    /**
     * @dev Queries the token balance of `_owner` at a specific `_blockNumber`.
     * @param _owner The address from which the balance will be retrieved.
     * @param _blockNumber The block number when the balance is queried.
     * @return The balance at `_blockNumber`.
     **/
    function balanceOfAt(address _owner, uint _blockNumber) external view returns (uint256);

    
    /**
     * @notice Get the current total vote power.
     * @return The current total vote power (sum of all accounts' vote powers).
     */
    function totalVotePower() external view returns(uint256);
    
    /**
    * @notice Get the total vote power at block `_blockNumber`
    * @param _blockNumber The block number at which to fetch.
    * @return The total vote power at the block  (sum of all accounts' vote powers).
    */
    function totalVotePowerAt(uint _blockNumber) external view returns(uint256);

    /**
     * @notice Get the current vote power of `_owner`.
     * @param _owner The address to get voting power.
     * @return Current vote power of `_owner`.
     */
    function votePowerOf(address _owner) external view returns(uint256);
    
    /**
    * @notice Get the vote power of `_owner` at block `_blockNumber`
    * @param _owner The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return Vote power of `_owner` at `_blockNumber`.
    */
    function votePowerOfAt(address _owner, uint256 _blockNumber) external view returns(uint256);

    /**
    * @notice Get the vote power of `_owner` at block `_blockNumber`, ignoring revocation information (and cache).
    * @param _owner The address to get voting power.
    * @param _blockNumber The block number at which to fetch.
    * @return Vote power of `_owner` at `_blockNumber`. Result doesn't change if vote power is revoked.
    */
    function votePowerOfAtIgnoringRevocation(address _owner, uint256 _blockNumber) external view returns(uint256);

    /**
     * @notice Get the delegation mode for '_who'. This mode determines whether vote power is
     *  allocated by percentage or by explicit value. Once the delegation mode is set, 
     *  it never changes, even if all delegations are removed.
     * @param _who The address to get delegation mode.
     * @return delegation mode: 0 = NOTSET, 1 = PERCENTAGE, 2 = AMOUNT (i.e. explicit)
     */
    function delegationModeOf(address _who) external view returns(uint256);
        
    /**
    * @notice Get current delegated vote power `_from` delegator delegated `_to` delegatee.
    * @param _from Address of delegator
    * @param _to Address of delegatee
    * @return The delegated vote power.
    */
    function votePowerFromTo(address _from, address _to) external view returns(uint256);
    
    /**
    * @notice Get delegated the vote power `_from` delegator delegated `_to` delegatee at `_blockNumber`.
    * @param _from Address of delegator
    * @param _to Address of delegatee
    * @param _blockNumber The block number at which to fetch.
    * @return The delegated vote power.
    */
    function votePowerFromToAt(address _from, address _to, uint _blockNumber) external view returns(uint256);
    
    /**
     * @notice Compute the current undelegated vote power of `_owner`
     * @param _owner The address to get undelegated voting power.
     * @return The unallocated vote power of `_owner`
     */
    function undelegatedVotePowerOf(address _owner) external view returns(uint256);
    
    /**
     * @notice Get the undelegated vote power of `_owner` at given block.
     * @param _owner The address to get undelegated voting power.
     * @param _blockNumber The block number at which to fetch.
     * @return The undelegated vote power of `_owner` (= owner's own balance minus all delegations from owner)
     */
    function undelegatedVotePowerOfAt(address _owner, uint256 _blockNumber) external view returns(uint256);
    
    /**
    * @notice Get the vote power delegation `delegationAddresses` 
    *  and `_bips` of `_who`. Returned in two separate positional arrays.
    * @param _who The address to get delegations.
    * @return _delegateAddresses Positional array of delegation addresses.
    * @return _bips Positional array of delegation percents specified in basis points (1/100 or 1 percent)
    * @return _count The number of delegates.
    * @return _delegationMode The mode of the delegation (NOTSET=0, PERCENTAGE=1, AMOUNT=2).
    */
    function delegatesOf(address _who)
        external view 
        returns (
            address[] memory _delegateAddresses,
            uint256[] memory _bips,
            uint256 _count, 
            uint256 _delegationMode
        );
        
    /**
    * @notice Get the vote power delegation `delegationAddresses` 
    *  and `pcts` of `_who`. Returned in two separate positional arrays.
    * @param _who The address to get delegations.
    * @param _blockNumber The block for which we want to know the delegations.
    * @return _delegateAddresses Positional array of delegation addresses.
    * @return _bips Positional array of delegation percents specified in basis points (1/100 or 1 percent)
    * @return _count The number of delegates.
    * @return _delegationMode The mode of the delegation (NOTSET=0, PERCENTAGE=1, AMOUNT=2).
    */
    function delegatesOfAt(address _who, uint256 _blockNumber)
        external view 
        returns (
            address[] memory _delegateAddresses, 
            uint256[] memory _bips, 
            uint256 _count, 
            uint256 _delegationMode
        );

    /**
     * Returns VPContract used for readonly operations (view methods).
     * The only non-view method that might be called on it is `revokeDelegationAt`.
     *
     * @notice `readVotePowerContract` is almost always equal to `writeVotePowerContract`
     * except during upgrade from one VPContract to a new version (which should happen
     * rarely or never and will be anounced before).
     *
     * @notice You shouldn't call any methods on VPContract directly, all are exposed
     * via VPToken (and state changing methods are forbidden from direct calls). 
     * This is the reason why this method returns `IVPContractEvents` - it should only be used
     * for listening to events (`Revoke` only).
     */
    function readVotePowerContract() external view returns (IVPContractEvents);

    /**
     * Returns VPContract used for state changing operations (non-view methods).
     * The only non-view method that might be called on it is `revokeDelegationAt`.
     *
     * @notice `writeVotePowerContract` is almost always equal to `readVotePowerContract`
     * except during upgrade from one VPContract to a new version (which should happen
     * rarely or never and will be anounced before). In the case of upgrade,
     * `writeVotePowerContract` will be replaced first to establish delegations, and
     * after some perio (e.g. after a reward epoch ends) `readVotePowerContract` will be set equal to it.
     *
     * @notice You shouldn't call any methods on VPContract directly, all are exposed
     * via VPToken (and state changing methods are forbidden from direct calls). 
     * This is the reason why this method returns `IVPContractEvents` - it should only be used
     * for listening to events (`Delegate` and `Revoke` only).
     */
    function writeVotePowerContract() external view returns (IVPContractEvents);
    
    /**
     * When set, allows token owners to participate in governance voting
     * and delegate governance vote power.
     */
    function governanceVotePower() external view returns (IGovernanceVotePower);
}
          

./contracts/utils/implementation/SafePct.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.7.6;
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";

/**
 * @dev Compute percentages safely without phantom overflows.
 *
 * Intermediate operations can overflow even when the result will always
 * fit into computed type. Developers usually
 * assume that overflows raise errors. `SafePct` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafePct {
    using SafeMath for uint256;
    /**
     * Requirements:
     *
     * - intermediate operations must revert on overflow
     */
    function mulDiv(uint256 x, uint256 y, uint256 z) internal pure returns (uint256) {
        require(z > 0, "Division by zero");

        if (x == 0) return 0;
        uint256 xy = x * y;
        if (xy / x == y) { // no overflow happened - same as in SafeMath mul
            return xy / z;
        }

        //slither-disable-next-line divide-before-multiply
        uint256 a = x / z;
        uint256 b = x % z; // x = a * z + b

        //slither-disable-next-line divide-before-multiply
        uint256 c = y / z;
        uint256 d = y % z; // y = c * z + d

        return (a.mul(c).mul(z)).add(a.mul(d)).add(b.mul(c)).add(b.mul(d).div(z));
    }
}
          

@openzeppelin/contracts/math/SafeMath.sol

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        uint256 c = a + b;
        if (c < a) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b > a) return (false, 0);
        return (true, a - b);
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) return (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a / b);
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a % b);
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: modulo by zero");
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        return a - b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a % b;
    }
}
          

@openzeppelin/contracts/token/ERC20/IERC20.sol

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
          

@openzeppelin/contracts/utils/SafeCast.sol

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;


/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCast {

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits");
        return int128(value);
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits");
        return int64(value);
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits");
        return int32(value);
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits");
        return int16(value);
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits");
        return int8(value);
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        require(value < 2**255, "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}
          

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"string","name":"_symbol","internalType":"string"},{"type":"uint256","name":"_decimals","internalType":"uint256"},{"type":"address","name":"_priceSubmitter","internalType":"contract IPriceSubmitter"},{"type":"address","name":"_wNat","internalType":"contract IIVPToken"},{"type":"address","name":"_ftsoManager","internalType":"address"},{"type":"uint256","name":"_firstEpochStartTs","internalType":"uint256"},{"type":"uint256","name":"_submitPeriodSeconds","internalType":"uint256"},{"type":"uint256","name":"_revealPeriodSeconds","internalType":"uint256"},{"type":"uint128","name":"_initialPriceUSD","internalType":"uint128"},{"type":"uint256","name":"_priceDeviationThresholdBIPS","internalType":"uint256"},{"type":"uint256","name":"_cyclicBufferSize","internalType":"uint256"}]},{"type":"event","name":"LowTurnout","inputs":[{"type":"uint256","name":"epochId","internalType":"uint256","indexed":true},{"type":"uint256","name":"natTurnout","internalType":"uint256","indexed":false},{"type":"uint256","name":"lowNatTurnoutThresholdBIPS","internalType":"uint256","indexed":false},{"type":"uint256","name":"timestamp","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PriceEpochInitializedOnFtso","inputs":[{"type":"uint256","name":"epochId","internalType":"uint256","indexed":true},{"type":"uint256","name":"endTime","internalType":"uint256","indexed":false},{"type":"uint256","name":"timestamp","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PriceFinalized","inputs":[{"type":"uint256","name":"epochId","internalType":"uint256","indexed":true},{"type":"uint256","name":"price","internalType":"uint256","indexed":false},{"type":"bool","name":"rewardedFtso","internalType":"bool","indexed":false},{"type":"uint256","name":"lowRewardPrice","internalType":"uint256","indexed":false},{"type":"uint256","name":"highRewardPrice","internalType":"uint256","indexed":false},{"type":"uint8","name":"finalizationType","internalType":"enum IFtso.PriceFinalizationType","indexed":false},{"type":"uint256","name":"timestamp","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PriceRevealed","inputs":[{"type":"address","name":"voter","internalType":"address","indexed":true},{"type":"uint256","name":"epochId","internalType":"uint256","indexed":true},{"type":"uint256","name":"price","internalType":"uint256","indexed":false},{"type":"uint256","name":"timestamp","internalType":"uint256","indexed":false},{"type":"uint256","name":"votePowerNat","internalType":"uint256","indexed":false},{"type":"uint256","name":"votePowerAsset","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"ASSET_PRICE_USD_DECIMALS","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"activateFtso","inputs":[{"type":"uint256","name":"_firstEpochStartTs","internalType":"uint256"},{"type":"uint256","name":"_submitPeriodSeconds","internalType":"uint256"},{"type":"uint256","name":"_revealPeriodSeconds","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"active","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IIFtso"}],"name":"assetFtsos","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IIVPToken"}],"name":"assets","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"configureEpochs","inputs":[{"type":"uint256","name":"_maxVotePowerNatThresholdFraction","internalType":"uint256"},{"type":"uint256","name":"_maxVotePowerAssetThresholdFraction","internalType":"uint256"},{"type":"uint256","name":"_lowAssetUSDThreshold","internalType":"uint256"},{"type":"uint256","name":"_highAssetUSDThreshold","internalType":"uint256"},{"type":"uint256","name":"_highAssetTurnoutThresholdBIPS","internalType":"uint256"},{"type":"uint256","name":"_lowNatTurnoutThresholdBIPS","internalType":"uint256"},{"type":"address[]","name":"_trustedAddresses","internalType":"address[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"deactivateFtso","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_maxVotePowerNatThresholdFraction","internalType":"uint256"},{"type":"uint256","name":"_maxVotePowerAssetThresholdFraction","internalType":"uint256"},{"type":"uint256","name":"_lowAssetUSDThreshold","internalType":"uint256"},{"type":"uint256","name":"_highAssetUSDThreshold","internalType":"uint256"},{"type":"uint256","name":"_highAssetTurnoutThresholdBIPS","internalType":"uint256"},{"type":"uint256","name":"_lowNatTurnoutThresholdBIPS","internalType":"uint256"},{"type":"address[]","name":"_trustedAddresses","internalType":"address[]"}],"name":"epochsConfiguration","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"fallbackFinalizePriceEpoch","inputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"address[]","name":"_eligibleAddresses","internalType":"address[]"},{"type":"uint256[]","name":"_natWeights","internalType":"uint256[]"},{"type":"uint256","name":"_natWeightsSum","internalType":"uint256"}],"name":"finalizePriceEpoch","inputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"},{"type":"bool","name":"_returnRewardData","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"forceFinalizePriceEpoch","inputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"ftsoManager","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IIVPToken"}],"name":"getAsset","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address[]","name":"","internalType":"contract IIFtso[]"}],"name":"getAssetFtsos","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getCurrentEpochId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_price","internalType":"uint256"},{"type":"uint256","name":"_timestamp","internalType":"uint256"}],"name":"getCurrentPrice","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_price","internalType":"uint256"},{"type":"uint256","name":"_priceTimestamp","internalType":"uint256"},{"type":"uint8","name":"_priceFinalizationType","internalType":"enum IFtso.PriceFinalizationType"},{"type":"uint256","name":"_lastPriceEpochFinalizationTimestamp","internalType":"uint256"},{"type":"uint8","name":"_lastPriceEpochFinalizationType","internalType":"enum IFtso.PriceFinalizationType"}],"name":"getCurrentPriceDetails","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_price","internalType":"uint256"},{"type":"uint256","name":"_timestamp","internalType":"uint256"}],"name":"getCurrentPriceFromTrustedProviders","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getCurrentRandom","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getEpochId","inputs":[{"type":"uint256","name":"_timestamp","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getEpochPrice","inputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getEpochPriceForVoter","inputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"},{"type":"address","name":"_voter","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_firstEpochStartTs","internalType":"uint256"},{"type":"uint256","name":"_submitPeriodSeconds","internalType":"uint256"},{"type":"uint256","name":"_revealPeriodSeconds","internalType":"uint256"}],"name":"getPriceEpochConfiguration","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"},{"type":"uint256","name":"_epochSubmitEndTime","internalType":"uint256"},{"type":"uint256","name":"_epochRevealEndTime","internalType":"uint256"},{"type":"uint256","name":"_votePowerBlock","internalType":"uint256"},{"type":"bool","name":"_fallbackMode","internalType":"bool"}],"name":"getPriceEpochData","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getRandom","inputs":[{"type":"uint256","name":"_epochId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address[]","name":"_assets","internalType":"contract IIVPToken[]"},{"type":"uint256[]","name":"_assetMultipliers","internalType":"uint256[]"},{"type":"uint256","name":"_totalVotePowerNat","internalType":"uint256"},{"type":"uint256","name":"_totalVotePowerAsset","internalType":"uint256"},{"type":"uint256","name":"_assetWeightRatio","internalType":"uint256"},{"type":"uint256","name":"_votePowerBlock","internalType":"uint256"}],"name":"getVoteWeightingParameters","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initializeCurrentEpochStateForReveal","inputs":[{"type":"uint256","name":"_circulatingSupplyNat","internalType":"uint256"},{"type":"bool","name":"_fallbackMode","internalType":"bool"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"priceDeviationThresholdBIPS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"priceEpochCyclicBufferSize","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IPriceSubmitter"}],"name":"priceSubmitter","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"revealPriceSubmitter","inputs":[{"type":"address","name":"_voter","internalType":"address"},{"type":"uint256","name":"_epochId","internalType":"uint256"},{"type":"uint256","name":"_price","internalType":"uint256"},{"type":"uint256","name":"_voterWNatVP","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setAsset","inputs":[{"type":"address","name":"_asset","internalType":"contract IIVPToken"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setAssetFtsos","inputs":[{"type":"address[]","name":"_assetFtsos","internalType":"contract IIFtso[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setVotePowerBlock","inputs":[{"type":"uint256","name":"_votePowerBlock","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateInitialPrice","inputs":[{"type":"uint256","name":"_initialPriceUSD","internalType":"uint256"},{"type":"uint256","name":"_initialPriceTimestamp","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IIVPToken"}],"name":"wNat","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"wNatVotePowerCached","inputs":[{"type":"address","name":"_owner","internalType":"address"},{"type":"uint256","name":"_epochId","internalType":"uint256"}]}]
              

Contract Creation Code

0x6101a06040523480156200001257600080fd5b506040516200556d3803806200556d83398181016040526101608110156200003957600080fd5b81019080805160405193929190846401000000008211156200005a57600080fd5b9083019060208201858111156200007057600080fd5b82516401000000008111828201881017156200008b57600080fd5b82525081516020918201929091019080838360005b83811015620000ba578181015183820152602001620000a0565b50505050905090810190601f168015620000e85780820380516001836020036101000a031916815260200191505b506040908152602082810151918301516060840151608085015160a086015160c087015160e08801516101008901516101208a0151610140909a01518d51999c50969a5094989397929691959094939290916200014b916001918e0190620001c9565b5060c0999099526001600160601b0319606098891b81166101805296881b8716610140529490961b9094166101605260e091909152610100526101209190915260028054426001600160801b03908116600160801b029481166001600160801b031990921691909117169290921790915560805260a0525062000275565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826200020157600085556200024c565b82601f106200021c57805160ff19168380011785556200024c565b828001600101855582156200024c579182015b828111156200024c5782518255916020019190600101906200022f565b506200025a9291506200025e565b5090565b5b808211156200025a57600081556001016200025f565b60805160a05160c05160e05161010051610120516101405160601c6101605160601c6101805160601c6151e26200038b60003980611aa4528061222c5280612e9c525080610a2c5280610a595280610c7d5280610f6e528061107f528061143c52806114c852806119e55280611b815280611e015280611eb652806120ff5250806118965280611a3f5280611fc952806120b2525080610b875280610ee45280611d6e528061401652806147e0525080610b665280610e4152806132425280613c13525080610b455280610d9e528061321252806132635280613bee5250806115835250806111e15280611d985280611e895280611ef652806133045280613a025250806111b65280611b3952506151e26000f3fe608060405234801561001057600080fd5b50600436106102325760003560e01c80639edbf00711610130578063d89601fd116100b8578063f025bf661161007c578063f025bf66146108ba578063f670ebe3146108c2578063f72cab28146108e7578063f7dba1f514610913578063f937d6ad146109da57610232565b8063d89601fd146107ed578063e3749e0c146107f5578063e3b3a3b314610860578063e536f39614610895578063eb91d37e146108b257610232565b8063c5d8b9e7116100ff578063c5d8b9e714610759578063cc245ab514610785578063cd4b69141461078d578063cf35bdd0146107aa578063d0d552dd146107c757610232565b80639edbf007146106f0578063a29a839f146106f8578063af52df0814610700578063c1f6c36e1461072157610232565b80634afd5102116101be5780637d1d6f12116101825780637d1d6f121461055f578063826cc76b1461057c5780638357d08c1461059957806395d89b4114610656578063974d7a6b146106d357610232565b80634afd5102146104fb5780635303548b14610518578063555989da146105475780635c222bad1461054f5780636b52b2421461055757610232565b8063144e159111610205578063144e15911461036c57806318931c35146103925780632f0a6f3c146103ea578063306ba2531461041357806340462a2d1461043657610232565b806302fb0c5e14610237578063040d73b81461025357806311a7aaaa146102a3578063131fdee2146102c7575b600080fd5b61023f6109e2565b604080519115158252519081900360200190f35b61025b6109eb565b6040518086815260200185815260200184600581111561027757fe5b815260200183815260200182600581111561028e57fe5b81526020019550505050505060405180910390f35b6102ab610a2a565b604080516001600160a01b039092168252519081900360200190f35b61036a600480360360208110156102dd57600080fd5b8101906020810181356401000000008111156102f857600080fd5b82018360208201111561030a57600080fd5b8035906020019184602083028401116401000000008311171561032c57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610a4e945050505050565b005b610374610b43565b60408051938452602084019290925282820152519081900360600190f35b61039a610bab565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156103d65781810151838201526020016103be565b505050509050019250505060405180910390f35b61036a6004803603606081101561040057600080fd5b5080359060208101359060400135610c72565b61036a6004803603604081101561042957600080fd5b5080359060200135610f63565b61045b6004803603604081101561044c57600080fd5b5080359060200135151561106f565b604051808060200180602001848152602001838103835286818151815260200191508051906020019060200280838360005b838110156104a557818101518382015260200161048d565b50505050905001838103825285818151815260200191508051906020019060200280838360005b838110156104e45781810151838201526020016104cc565b505050509050019550505050505060405180910390f35b61036a6004803603602081101561051157600080fd5b5035611431565b6105356004803603602081101561052e57600080fd5b5035611498565b60408051918252519081900360200190f35b61036a6114ab565b6102ab611501565b610535611581565b6105356004803603602081101561057557600080fd5b50356115a5565b6102ab6004803603602081101561059257600080fd5b50356115ba565b6105a16115e4565b604051808060200180602001878152602001868152602001858152602001848152602001838103835289818151815260200191508051906020019060200280838360005b838110156105fd5781810151838201526020016105e5565b50505050905001838103825288818151815260200191508051906020019060200280838360005b8381101561063c578181015183820152602001610624565b505050509050019850505050505050505060405180910390f35b61065e61194d565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610698578181015183820152602001610680565b50505050905090810190601f1680156106c55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61036a600480360360208110156106e957600080fd5b50356119da565b6102ab611a3d565b610535611a61565b610708611a6c565b6040805192835260208301919091528051918290030190f35b61036a6004803603608081101561073757600080fd5b506001600160a01b038135169060208101359060408101359060600135611a87565b6105356004803603604081101561076f57600080fd5b50803590602001356001600160a01b0316611ae3565b610535611b37565b610535600480360360208110156107a357600080fd5b5035611b5b565b6102ab600480360360208110156107c057600080fd5b5035611b66565b61036a600480360360208110156107dd57600080fd5b50356001600160a01b0316611b76565b610535611c7b565b6107fd611ca9565b6040518088815260200187815260200186815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019060200280838360008381101561063c578181015183820152602001610624565b610868611d4f565b60408051958652602086019490945284840192909252606084015215156080830152519081900360a00190f35b61036a600480360360208110156108ab57600080fd5b5035611df6565b610708611e6c565b610535611e87565b61036a600480360360408110156108d857600080fd5b50803590602001351515611eab565b610535600480360360408110156108fd57600080fd5b506001600160a01b0381351690602001356120ab565b61036a600480360360e081101561092957600080fd5b81359160208101359160408201359160608101359160808201359160a08101359181019060e0810160c082013564010000000081111561096857600080fd5b82018360208201111561097a57600080fd5b8035906020019184602083028401116401000000008311171561099c57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506120f4945050505050565b6102ab61222a565b60005460ff1681565b6002546004546001600160801b0380831693600160801b909304169160ff8083169261010081046001600160f01b031692600160f81b90910490911690565b7f000000000000000000000000000000000000000000000000000000000000000081565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a8657610a8661224e565b6000815111610a9157fe5b600181511180610ac75750306001600160a01b031681600081518110610ab357fe5b60200260200101516001600160a01b031614155b610acd57fe5b8051610ae0906011906020840190614fa1565b5080516001600160401b0381118015610af857600080fd5b50604051908082528060200260200182016040528015610b22578160200160208202803683370190505b508051610b3791601091602090910190614fa1565b50610b406122b6565b50565b7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000909192565b6010546060906001148015610bc257506011546001145b8015610bf85750306001600160a01b03166011600081548110610be157fe5b6000918252602090912001546001600160a01b0316145b610c5c576011805480602002602001604051908101604052809291908181526020018280548015610c5257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610c34575b5050505050610c6c565b6040805160008152602081019091525b90505b90565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610caa57610caa61224e565b600054604080518082019091526016815275119514d3c8185b1c9958591e481858dd1a5d985d195960521b60208201529060ff1615610d675760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610d2c578181015183820152602001610d14565b50505050905090810190601f168015610d595780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f00000000000000000000000000000000000000000000000000000000000000008414610e0a5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f00000000000000000000000000000000000000000000000000000000000000008314610ead5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f00000000000000000000000000000000000000000000000000000000000000008214610f505760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50506000805460ff191660011790555050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f9b57610f9b61224e565b600054604080518082019091526016815275119514d3c8185b1c9958591e481858dd1a5d985d195960521b60208201529060ff161561101b5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5061102582612492565b600280546001600160801b0319166001600160801b039290921691909117905561104e81612492565b600280546001600160801b03928316600160801b0292169190911790555050565b6060806000336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146110ac576110ac61224e565b60006110b7866124da565b60018101805460ff60f01b191690556005810154909150600090156110ef578160050154612710836008015402816110eb57fe5b0490505b6001820154600160f81b900460ff168061110d575081600401548111155b15611180576001820154600160f81b900460ff1661116d57600482015460408051838152602081019290925242828201525188917fbece8aa526cdc5e528cdaa56c1d03edc19da51e41845aa146f64a7071f74c65a919081900360600190a25b61117987836000612614565b505061142a565b606080606061118e85612835565b91945092509050606061119f615002565b6111a985856129ca565b61012081015191935091507f000000000000000000000000000000000000000000000000000000000000000090611205906005908f907f0000000000000000000000000000000000000000000000000000000000000000612ce8565b1115611223576112178c886000612614565b5050505050505061142a565b60108701805460ff199081166001908117909255610120830151600f8a01819055600280546001600160801b0319166001600160801b03928316178216600160801b429384160217905560048054600160f81b6001600160f81b036001600160f01b0390941661010002610100600160f81b031992909516909517169290921716919091179055600e870154156113405761130f87600e0180548060200260200160405190810160405280929190818152602001828054801561130557602002820191906000526020600020905b8154815260200190600101908083116112f1575b5050505050612d74565b60038054426001600160801b03908116600160801b029381166001600160801b031990921691909117169190911790555b60008b156113785760006113538e612e98565b90506113628984838789612f58565b8251929e50909c509a501561137657600191505b505b6113848d838584611add565b8c7ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3089600f0154838561014001518661016001518d60100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156113f557fe5b8152602001828152602001965050505050505060405180910390a2505050600190940180546001600160f81b03169055505050505b9250925092565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114695761146961224e565b6000611474826124da565b6001808201805460ff60f01b191690559091506114949083908390612614565b5050565b60006114a38261320e565b90505b919050565b60005460ff166114bd576114bd613293565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114f5576114f561224e565b6000805460ff19169055565b601054600090600114801561151857506011546001145b801561154e5750306001600160a01b0316601160008154811061153757fe5b6000918252602090912001546001600160a01b0316145b611559576000610c6c565b601060008154811061156757fe5b6000918252602090912001546001600160a01b0316905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b60006115b0826132fd565b600f015492915050565b601181815481106115ca57600080fd5b6000918252602090912001546001600160a01b0316905081565b606080600080600080601080548060200260200160405190810160405280929190818152602001828054801561164357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611625575b50506007548451949a506001600160f01b031694506000939250506001600160401b0382119050801561167557600080fd5b5060405190808252806020026020018201604052801561169f578160200160208202803683370190505b509050600087516001600160401b03811180156116bb57600080fd5b506040519080825280602002602001820160405280156116e5578160200160208202803683370190505b50905060005b88518110156118745760006001600160a01b031689828151811061170b57fe5b60200260200101516001600160a01b031614156117295760006117ad565b88818151811061173557fe5b60200260200101516001600160a01b0316633e5aa26a856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561178057600080fd5b505afa158015611794573d6000803e3d6000fd5b505050506040513d60208110156117aa57600080fd5b50515b8382815181106117b957fe5b602002602001018181525050601181815481106117d257fe5b9060005260206000200160009054906101000a90046001600160a01b03166001600160a01b031663eb91d37e6040518163ffffffff1660e01b8152600401604080518083038186803b15801561182757600080fd5b505afa15801561183b573d6000803e3d6000fd5b505050506040513d604081101561185157600080fd5b5051825183908390811061186157fe5b60209081029190910101526001016116eb565b50600061188460058a85856133c5565b905061189260058a8361356b565b97507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316633e5aa26a856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156118f857600080fd5b505afa15801561190c573d6000803e3d6000fd5b505050506040513d602081101561192257600080fd5b5051965061193360058a858461369f565b9550611940600587613767565b9450505050909192939495565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156119d25780601f106119a7576101008083540402835291602001916119d2565b820191906000526020600020905b8154815290600101906020018083116119b557829003601f168201915b505050505081565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611a1257611a1261224e565b6000611a1d826124da565b6001808201805460ff60f01b1916905590915061149490839083906137bc565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000610c6c4261320e565b6003546001600160801b0380821691600160801b9004169091565b60005460ff16611a9957611a99613293565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611ad157611ad161224e565b611add848484846138f7565b50505050565b600080611aef846132fd565b90506000611afd8285613b94565b905080611b0f57600092505050611b31565b600019016000908152600d90910160205260409020546001600160801b031690505b92915050565b7f000000000000000000000000000000000000000000000000000000000000000081565b60006114a382612e98565b601081815481106115ca57600080fd5b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611bae57611bae61224e565b6040805160208101909152308152611bca906011906001614fa1565b5060408051602081019091526001600160a01b0382168152611bf0906010906001614fa1565b50806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015611c2a57600080fd5b505afa158015611c3e573d6000803e3d6000fd5b505050506040513d6020811015611c5457600080fd5b50516001600160a01b03909116600090815260066020526040902060ff909116600a0a9055565b600080611c86611a61565b905080611c97576000915050610c6f565b611ca360018203612e98565b91505090565b60008060008060008060606005600301546005600401546005800154600560060154600560070154600560080154600560090180805480602002602001604051908101604052809291908181526020018280548015611d3157602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611d13575b50505050509050965096509650965096509650965090919293949596565b6000806000806000611d5f611a61565b9450611d6a85613bec565b93507f00000000000000000000000000000000000000000000000000000000000000008401925060006005817f00000000000000000000000000000000000000000000000000000000000000008881611dbf57fe5b0681526020810191909152604001600020600101549596949593946001600160f01b03851694600160f81b900460ff169350915050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611e2e57611e2e61224e565b438110611e3a57600080fd5b600160f01b8110611e4a57600080fd5b600780546001600160f01b0319166001600160f01b0392909216919091179055565b6002546001600160801b0380821691600160801b9004169091565b7f000000000000000000000000000000000000000000000000000000000000000081565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611ee357611ee361224e565b6000611eed611a61565b905060006005817f00000000000000000000000000000000000000000000000000000000000000008481611f1d57fe5b0681526020810191909152604001600090812060108101805460ff1916905560088101829055600c81018290556007546001820180546001600160f01b039092166001600160f01b0319909216919091176001600160f81b0316600160f81b87151502179055600281018490559150611f9a90600e830190615063565b8215611fa7575050611494565b606080611fb2613c39565b6007549194509250612062915084908890611ff7907f0000000000000000000000000000000000000000000000000000000000000000906001600160f01b0316613e2f565b601080548060200260200160405190810160405280929190818152602001828054801561204d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161202f575b5060059796959493508a925089915050613ec0565b837fc0eaa359541c7c642d9947c9496507c134f3e4f8e1fd433313eb27dc48cb1fb761208d86613bec565b604080519182524260208301528051918290030190a2505050505050565b60006120ed7f0000000000000000000000000000000000000000000000000000000000000000846120db856132fd565b600101546001600160f01b0316613f6f565b9392505050565b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461212c5761212c61224e565b60088790556009869055600a859055600b849055600c839055600d829055600e5460005b818110156121ad5760006005600a0160006005600901848154811061217157fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055600101612150565b5050805160005b8181101561220b5760016005600a0160008584815181106121d157fe5b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff19169115159190911790556001016121b4565b50815161221f90600e906020850190614fa1565b505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b604080518082018252600d81526c1058d8d95cdcc819195b9a5959609a1b6020808301918252925162461bcd60e51b81526004810193845282516024820152825192939283926044909201919080838360008315610d2c578181015183820152602001610d14565b60115460011480156122f25750306001600160a01b031660116000815481106122db57fe5b6000918252602090912001546001600160a01b0316145b156122fc57612490565b60005b601154811015610b405760006011828154811061231857fe5b6000918252602091829020015460408051635c222bad60e01b815290516001600160a01b0390921692635c222bad92600480840193829003018186803b15801561236157600080fd5b505afa158015612375573d6000803e3d6000fd5b505050506040513d602081101561238b57600080fd5b505160108054919250908390811061239f57fe5b6000918252602090912001546001600160a01b03828116911614156123c45750612488565b80601083815481106123d257fe5b600091825260209091200180546001600160a01b0319166001600160a01b0392831617905581161561248657806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561243757600080fd5b505afa15801561244b573d6000803e3d6000fd5b505050506040513d602081101561246157600080fd5b50516001600160a01b038216600090815260066020526040902060ff909116600a0a90555b505b6001016122ff565b565b6000600160801b82106124d65760405162461bcd60e51b81526004018080602001828103825260278152602001806151656027913960400191505060405180910390fd5b5090565b60006124e582614012565b4210156040518060400160405280602081526020017f45706f6368206e6f7420726561647920666f722066696e616c697a6174696f6e8152509061256a5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50612574826132fd565b90506000601082015460ff16600581111561258b57fe5b146040518060400160405280601781526020017f45706f636820616c72656164792066696e616c697a65640000000000000000008152509061260e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50919050565b600e820154156128255761267782600e0180548060200260200160405190810160405280929190818152602001828054801561130557602002820191906000526020600020908154815260200190600101908083116112f1575050505050612d74565b600f8301558061268857600261268b565b60045b60108301805460ff191660018360058111156126a357fe5b0217905550600f82015460028054426001600160801b03908116600160801b029381166001600160801b0319909216919091171691909117905560108201546004805460ff9092169160ff191660018360058111156126fe57fe5b021790555060048054610100600160f81b031916610100426001600160f01b03160217808255601084015460ff1691906001600160f81b0316600160f81b83600581111561274857fe5b0217905550600f82015460038054426001600160801b03908116600160801b029381166001600160801b0319909216919091171691909117905561278b83610b40565b827ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3083600f015460008060008760100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156127f457fe5b8152602001828152602001965050505050505060405180910390a26001820180546001600160f81b03169055612830565b6128308383836137bc565b505050565b6060806060600084600c01549050806001600160401b038111801561285957600080fd5b50604051908082528060200260200182016040528015612883578160200160208202803683370190505b509350806001600160401b038111801561289c57600080fd5b506040519080825280602002602001820160405280156128c6578160200160208202803683370190505b5091506000816001600160401b03811180156128e157600080fd5b5060405190808252806020026020018201604052801561290b578160200160208202803683370190505b50905060005b828110156129b3576000818152600d880160205260409020805487516001600160801b039091169088908490811061294557fe5b602090810291909101015280548551600160801b9091046001600160401b03169086908490811061297257fe5b602090810291909101015280548351600160c01b9091046001600160401b03169084908490811061299f57fe5b602090810291909101015250600101612911565b506129bf868483614045565b935050509193909250565b60606129d4615002565b8351806001600160401b03811180156129ec57600080fd5b50604051908082528060200260200182016040528015612a16578160200160208202803683370190505b50925060005b81811015612a445780848281518110612a3157fe5b6020908102919091010152600101612a1c565b50612a5b6002600060018403600080888b8b61415c565b608085015260608401528083528351859185918110612a7657fe5b602002602001015181518110612a8857fe5b602090810291909101015160a0830181905260808301516060840151845192010190612ac1578251600060c08501526020840152612b15565b60048104836060015111612ae5578251606084015160c08501526020840152612b15565b612b0960016000600186600001510360008760a00151886080015101898c8c61415c565b5060c085015260208401525b825160001983011415612b3657825160006101008501526040840152612b8e565b60048104836080015111612b5b57825160808401516101008501526040840152612b8e565b612b8160038460000151600101600185038660a001518760600151016000898c8c61415c565b6101008601525060408401525b8584846000015181518110612b9f57fe5b602002602001015181518110612bb157fe5b6020908102919091010151610120840152600281048360a00151846060015101148015612bdf575060028106155b15612c0f576002612bf9846000015160018503878a614437565b8461012001510181612c0757fe5b046101208401525b612c29836020015160006000198660c00151888b8b6144f7565b60c085015260208401526040830151610100840151612c549190600019850190600190888b8b6144f7565b610100850152604084015260208301518451879186918110612c7257fe5b602002602001015181518110612c8457fe5b6020026020010151836101400181815250508584846040015181518110612ca757fe5b602002602001015181518110612cb957fe5b602090810291909101015161016084015261010083015160c08401519091030360e08301525090939092509050565b600083612cf757506000612d6c565b6000858184600019880181612d0857fe5b068152602001908152602001600020600f0154905080841415612d2f576000915050612d6c565b83612d425764e8d4a51000915050612d6c565b600084821115612d555750838103612d5a565b508084035b612d6781612710876145cb565b925050505b949350505050565b805160009080612d8057fe5b60015b81811015612e2c576000848281518110612d9957fe5b6020026020010151905060008290505b600081118015612dce575081866001830381518110612dc457fe5b6020026020010151115b15612e0a57856001820381518110612de257fe5b6020026020010151868281518110612df657fe5b602090810291909101015260001901612da9565b81868281518110612e1757fe5b60209081029190910101525050600101612d83565b506002810460018083161415612e5957838181518110612e4857fe5b6020026020010151925050506114a6565b6002848281518110612e6757fe5b6020026020010151856001840381518110612e7e57fe5b60200260200101510181612e8e57fe5b04925050506114a6565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663cd4b6914836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015612efe57600080fd5b505afa158015612f12573d6000803e3d6000fd5b505050506040513d6020811015612f2857600080fd5b50516040805160208082019390935230818301528151808203830181526060909101909152805191012092915050565b6020840151606090819060009081905b88604001518111613033576000878281518110612f8157fe5b602002602001015190506000878281518110612f9957fe5b60200260200101511115613029576000818152600d8c0160205260409020546101408b01516001600160801b0390911690811480612fe457508a6101600151816001600160801b0316145b801561301557506000828152600d8d016020526040902060010154613013908b906001600160a01b03166146d7565b155b1561302157505061302b565b506001909201915b505b600101612f68565b50806001600160401b038111801561304a57600080fd5b50604051908082528060200260200182016040528015613074578160200160208202803683370190505b509350806001600160401b038111801561308d57600080fd5b506040519080825280602002602001820160405280156130b7578160200160208202803683370190505b5060208901519093506000905b896040015181116132005760008882815181106130dd57fe5b6020026020010151905060008882815181106130f557fe5b6020026020010151905060008111156131f5576000828152600d8e0160205260409020546101408d01516001600160801b039091169081148061314557508c6101600151816001600160801b0316145b801561317657506000838152600d8f016020526040902060010154613174908d906001600160a01b03166146d7565b155b15613183575050506131f8565b6000838152600d8f01602052604090206001015489516001600160a01b03909116908a90879081106131b157fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818886815181106131de57fe5b602090810291909101015250948501946001909301925b50505b6001016130c4565b505050955095509592505050565b60007f0000000000000000000000000000000000000000000000000000000000000000821015613240575060006114a6565b7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000083038161328b57fe5b0490506114a6565b604080518082018252600f81526e4654534f206e6f742061637469766560881b6020808301918252925162461bcd60e51b81526004810193845282516024820152825192939283926044909201919080838360008315610d2c578181015183820152602001610d14565b60006005817f0000000000000000000000000000000000000000000000000000000000000000848161332b57fe5b0681526020019081526020016000209050806002015482146040518060400160405280601881526020017f45706f63682064617461206e6f7420617661696c61626c6500000000000000008152509061260e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b82516060906000816001600160401b03811180156133e257600080fd5b5060405190808252806020026020018201604052801561340c578160200160208202803683370190505b5090506000805b838110156134f55760006001600160a01b031688828151811061343257fe5b60200260200101516001600160a01b0316141561344e576134ed565b60006134c587838151811061345f57fe5b60200260200101518b60010160008c868151811061347957fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020548a85815181106134ae57fe5b60200260200101516145cb9092919063ffffffff16565b9050808483815181106134d457fe5b60209081029190910101526134e98382614712565b9250505b600101613413565b5080156135605760005b8381101561355e5761353f61353261271088848151811061351c57fe5b602002602001015161476c90919063ffffffff16565b838584815181106134ae57fe5b83828151811061354b57fe5b60209081029190910101526001016134ff565b505b509695505050505050565b8151606090806001600160401b038111801561358657600080fd5b506040519080825280602002602001820160405280156135b0578160200160208202803683370190505b50915060005b818110156136965760006001600160a01b03168582815181106135d557fe5b60200260200101516001600160a01b0316146136735760008660010160008784815181106135ff57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002054905061365561271064e8d4a510008161363d57fe5b04670de0b6b3a764000002828785815181106134ae57fe5b84838151811061366157fe5b6020026020010181815250505061368e565b600083828151811061368157fe5b6020026020010181815250505b6001016135b6565b50509392505050565b600080805b855181101561375d5760006001600160a01b03168682815181106136c457fe5b60200260200101516001600160a01b03161461375557600061271061373d8784815181106136ee57fe5b60200260200101518a60010160008b878151811061370857fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020548886815181106134ae57fe5b8161374457fe5b0490506137518382614712565b9250505b6001016136a4565b5095945050505050565b600080836005015483101561377e575060006120ed565b8360060154831061379257506113886120ed565b600584015460068501546101f4919081900390850361119402816137b257fe5b0401949350505050565b82156137d8576002546001600160801b0316600f8301556137e0565b6000600f8301555b806137ec5760036137ef565b60055b60108301805460ff1916600183600581111561380757fe5b021790555060048054610100600160f81b031916610100426001600160f01b03160217808255601084015460ff1691906001600160f81b0316600160f81b83600581111561385157fe5b021790555061385f83610b40565b827ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3083600f015460008060008760100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156138c857fe5b8152602001828152602001965050505050505060405180910390a25060010180546001600160f81b0316905550565b60408051808201909152600e81526d0a0e4d2c6ca40e8dede40d0d2ced60931b6020820152600160801b831061396e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50613978836147c5565b6040518060400160405280601881526020017f52657665616c20706572696f64206e6f74206163746976650000000000000000815250906139fa5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060006005817f00000000000000000000000000000000000000000000000000000000000000008681613a2957fe5b0681526020810191909152604001600020600181015490915060ff600160f81b8204811691600160f01b8104909116906001600160f01b03168180613a8e5750828015613a8e57506001600160a01b0388166000908152600f602052604090205460ff165b6040518060400160405280602081526020017f45706f6368206e6f7420696e697469616c697a656420666f722072657665616c81525090613b105760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50600080613b21868b898887614808565b9092509050613b356005878c85858d614868565b604080518981524260208201528082018490526060810183905290518a916001600160a01b038d16917fc1b1d37612887c207efe8cb44f4069b7f10c45edaf6e8405648a94e02b6e9ec79181900360800190a350505050505050505050565b600c820154600090815b81811015613be1576000818152600d860160205260409020600101546001600160a01b0385811691161415613bd9576001019150611b319050565b600101613b9e565b506000949350505050565b7f0000000000000000000000000000000000000000000000000000000000000000600182017f00000000000000000000000000000000000000000000000000000000000000000201919050565b6060806060613c466122b6565b6010805480602002602001604051908101604052809291908181526020018280548015613c9c57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613c7e575b5050505050925082516001600160401b0381118015613cba57600080fd5b50604051908082528060200260200182016040528015613ce4578160200160208202803683370190505b50915082516001600160401b0381118015613cfe57600080fd5b50604051908082528060200260200182016040528015613d28578160200160208202803683370190505b50905060005b8351811015613e2957613d62848281518110613d4657fe5b60209081029190910101516007546001600160f01b0316613e2f565b838281518110613d6e57fe5b60200260200101818152505060118181548110613d8757fe5b9060005260206000200160009054906101000a90046001600160a01b03166001600160a01b031663eb91d37e6040518163ffffffff1660e01b8152600401604080518083038186803b158015613ddc57600080fd5b505afa158015613df0573d6000803e3d6000fd5b505050506040513d6040811015613e0657600080fd5b50518251839083908110613e1657fe5b6020908102919091010152600101613d2e565b50909192565b60006001600160a01b038316613e4757506000611b31565b826001600160a01b031663caeb942b836040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b158015613e8d57600080fd5b505af1158015613ea1573d6000803e3d6000fd5b505050506040513d6020811015613eb757600080fd5b50519050611b31565b613ecd87878585856149b8565b6007870154600380880191909155600888015460048801556005870186905560068701859055870154613f08908581613f0257fe5b04612492565b86546001600160801b0319166001600160801b039190911617865560048701546007870154613f39919081613f0257fe5b86546001600160801b03918216600160801b0291161786555050506001909201805460ff60f01b1916600160f01b179055505050565b60006001600160a01b038416613f87575060006120ed565b836001600160a01b031663e587497e84846040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015613fde57600080fd5b505af1158015613ff2573d6000803e3d6000fd5b505050506040513d602081101561400857600080fd5b5051949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000061403e83613bec565b0192915050565b600c830154606090806001600160401b038111801561406357600080fd5b5060405190808252806020026020018201604052801561408d578160200160208202803683370190505b509150600061409b85614a21565b905060006140a885614a21565b90506000806140b8898585614a66565b905083156140c857806127100391505b60005b8581101561414f57600083156141115761410e8a83815181106140ea57fe5b602002602001015164e8d4a51000026127108802866145cb9092919063ffffffff16565b90505b6000831561412b576141288a84815181106140ea57fe5b90505b80820189848151811061413a57fe5b602090810291909101015250506001016140cb565b5050505050509392505050565b6000806000888a1415614176575088915086905085614429565b61417e615081565b888152602081018890526141906150be565b60208082018d905260408083018d90528051448184015242818301528151808203830181526060909101909152805191012060005b602083015160408401516141f791908160018183030186816141e357fe5b0601876000015188602001518f8f8f614acd565b606087015260408601528084528a518b9190811061421157fe5b60200260200101518360600181815250508783606001518151811061423257fe5b6020026020010151846080018181525050836060015184604001518560800151010190508e6002141561430d5760028106600282040160a0850181905280820360c086015260408501511080159061428d57508b8460a00151115b156142b257825160001901604084015260608401516080850151016020850152614308565b8360c0015184606001511180156142cc57508a8460c00151115b156142ed578251600101602084015260408401516080850151018452614308565b50505160408201516060909201519094509092509050614429565b614424565b8e6001141561439e576004810460a0850181905280820360c0860152604085015111801561433e57508b8460a00151115b1561436357825160001901604084015260608401516080850151016020850152614308565b8360c001518460600151101580156142cc57508a8460c0015111156142ed578251600101602084015260408401516080850151018452614308565b6004810460c08501819052810360a085018190526040850151108015906143c857508b8460a00151115b156143ed57825160001901604084015260608401516080850151016020850152614424565b8360c00151846060015111801561440757508a8460c00151115b156142ed5782516001016020840152604084015160808501510184525b6141c5565b985098509895505050505050565b60008385141561446f578183868151811061444e57fe5b60200260200101518151811061446057fe5b60200260200101519050612d6c565b60008284876001018151811061448157fe5b60200260200101518151811061449357fe5b602002602001015190506000808760020190505b8681116144eb57848682815181106144bb57fe5b6020026020010151815181106144cd57fe5b60200260200101519150828210156144e3578192505b6001016144a7565b50909695505050505050565b60008085888a038802821361451257898792509250506145bf565b600085878c8151811061452157fe5b60200260200101518151811061453357fe5b602090810291909101015190508a89016000815b60008c8e830302136145b35789818151811061455f57fe5b602002602001015191508389838151811061457657fe5b602002602001015114156145ac5787828151811061459057fe5b6020026020010151850394506145a783828c614c96565b918b01915b8b01614547565b50508990039350909150505b97509795505050505050565b6000808211614614576040805162461bcd60e51b815260206004820152601060248201526f4469766973696f6e206279207a65726f60801b604482015290519081900360640190fd5b83614621575060006120ed565b8383028385828161462e57fe5b0414156146475782818161463e57fe5b049150506120ed565b600083868161465257fe5b049050600084878161466057fe5b069050600085878161466e57fe5b049050600086888161467c57fe5b0690506146ca61469688614690868561476c565b90614cfe565b6146c46146a3868661476c565b6146c46146b0898761476c565b6146c48d6146be8c8b61476c565b9061476c565b90614712565b9998505050505050505050565b604080516020808201949094526001600160a01b03929092168282015280518083038201815260609092019052805191012060019081161490565b6000828201838110156120ed576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60008261477b57506000611b31565b8282028284828161478857fe5b04146120ed5760405162461bcd60e51b815260040180806020018281038252602181526020018061518c6021913960400191505060405180910390fd5b6000806147d183613bec565b90504281111580156120ed57507f000000000000000000000000000000000000000000000000000000000000000001421092915050565b600080831561481c5750600090508061485e565b84915061482a878785614d65565b87549091506001600160801b0380821691600160801b9004168184111561484f578193505b8083111561485b578092505b50505b9550959350505050565b600085600c0154905060006148898686868a600601548b6007015488614e23565b63ffffffff838116608083019081526000858152600d8b0160209081526040918290208551815492870151938701516001600160801b03199093166001600160801b039091161767ffffffffffffffff60801b1916600160801b6001600160401b0394851602176001600160c01b0316600160c01b939092169290920217815560608401516001918201805493516001600160a01b03199094166001600160a01b039092169190911763ffffffff60a01b1916600160a01b93851693909302929092179091558401600c8a0155600889015491925061496b9190879061471216565b60088801556001600160a01b0386166000908152600a8901602052604090205460ff16156149ae57600e8701805460018101825560009182526020909120018390555b5050505050505050565b82516149cd90600a8601906020860190614fa1565b506149da858484846133c5565b80516149f091600b8701916020909101906150e6565b5060006149fe868685614e80565b600786018190559050614a118682613767565b8560090181905550505050505050565b600080805b8351811015614a5f57614a55848281518110614a3e57fe5b60200260200101518361471290919063ffffffff16565b9150600101614a26565b5092915050565b600081614a75575060006120ed565b82614a8357506127106120ed565b6000614a978361271064e8d4a510006145cb565b905084600301548110614ab057505060098301546120ed565b60038501546009860154614ac59183906145cb565b9150506120ed565b60008060008085878b81518110614ae057fe5b602002602001015181518110614af257fe5b60200260200101519050600060026001600160401b0381118015614b1557600080fd5b50604051908082528060200260200182016040528015614b3f578160200160208202803683370190505b5090508981600081518110614b5057fe5b6020026020010181815250508881600181518110614b6a57fe5b60209081029190910101528c8c614b828d828c614c96565b81805b82811015614c465760008c8281518110614b9b57fe5b60200260200101519050868c8281518110614bb257fe5b60200260200101511015614c09578a8181518110614bcc57fe5b602002602001015186600081518110614be157fe5b602002602001018181510191508181525050614bfe83838f614c96565b600190920191614c3d565b8a8181518110614c1557fe5b602002602001015186600181518110614c2a57fe5b6020026020010181815101915081815250505b50600101614b85565b50614c5282828d614c96565b8084600081518110614c6057fe5b602002602001015185600181518110614c7557fe5b60200260200101519750975097505050505050985098509895505050505050565b81831415614ca357612830565b808281518110614caf57fe5b6020026020010151818481518110614cc357fe5b6020026020010151828581518110614cd757fe5b60200260200101838581518110614cea57fe5b602090810291909101019190915252505050565b6000808211614d54576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381614d5d57fe5b049392505050565b600a83015460009081906001600160401b0381118015614d8457600080fd5b50604051908082528060200260200182016040528015614dae578160200160208202803683370190505b50905060005b600a860154811015614e0d57614dee86600a018281548110614dd257fe5b6000918252602090912001546001600160a01b03168686613f6f565b828281518110614dfa57fe5b6020908102919091010152600101614db4565b50614e1a60058683614e80565b95945050505050565b614e2b615121565b6001600160a01b0387166060820152614e448685614f70565b6001600160401b03166020820152614e5c8584614f70565b6001600160401b031660408201526001600160801b03909116815295945050505050565b600080805b600a850154811015614f675760006001600160a01b031685600a018281548110614eab57fe5b6000918252602090912001546001600160a01b03161415614ecb57614f5f565b614f5c612710614f4c868481518110614ee057fe5b60200260200101518960010160008a600a018781548110614efd57fe5b60009182526020808320909101546001600160a01b03168352820192909252604001902054600b8a01805487908110614f3257fe5b90600052602060002001546145cb9092919063ffffffff16565b81614f5357fe5b84919004614712565b91505b600101614e85565b50949350505050565b6000811580614f7d575082155b15614f8a57506000611b31565b614f9a8364e8d4a51000846145cb565b9050611b31565b828054828255906000526020600020908101928215614ff6579160200282015b82811115614ff657825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614fc1565b506124d692915061514f565b6040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b5080546000825590600052602060002090810190610b40919061514f565b6040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215614ff6579160200282015b82811115614ff6578251825591602001919060010190615106565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b5b808211156124d6576000815560010161515056fe53616665436173743a2076616c756520646f65736e27742066697420696e203132382062697473536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220a0feb28364f05773bf3bdc810cf6412e41f6fe7ef2c4a4adf535c3c83494e52564736f6c634300070600330000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000500000000000000000000000010000000000000000000000000000000000000030000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028940000000000000000000000000000000000000000000000000000000062cf1e4600000000000000000000000000000000000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc00000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000346494c0000000000000000000000000000000000000000000000000000000000

Deployed ByteCode

0x608060405234801561001057600080fd5b50600436106102325760003560e01c80639edbf00711610130578063d89601fd116100b8578063f025bf661161007c578063f025bf66146108ba578063f670ebe3146108c2578063f72cab28146108e7578063f7dba1f514610913578063f937d6ad146109da57610232565b8063d89601fd146107ed578063e3749e0c146107f5578063e3b3a3b314610860578063e536f39614610895578063eb91d37e146108b257610232565b8063c5d8b9e7116100ff578063c5d8b9e714610759578063cc245ab514610785578063cd4b69141461078d578063cf35bdd0146107aa578063d0d552dd146107c757610232565b80639edbf007146106f0578063a29a839f146106f8578063af52df0814610700578063c1f6c36e1461072157610232565b80634afd5102116101be5780637d1d6f12116101825780637d1d6f121461055f578063826cc76b1461057c5780638357d08c1461059957806395d89b4114610656578063974d7a6b146106d357610232565b80634afd5102146104fb5780635303548b14610518578063555989da146105475780635c222bad1461054f5780636b52b2421461055757610232565b8063144e159111610205578063144e15911461036c57806318931c35146103925780632f0a6f3c146103ea578063306ba2531461041357806340462a2d1461043657610232565b806302fb0c5e14610237578063040d73b81461025357806311a7aaaa146102a3578063131fdee2146102c7575b600080fd5b61023f6109e2565b604080519115158252519081900360200190f35b61025b6109eb565b6040518086815260200185815260200184600581111561027757fe5b815260200183815260200182600581111561028e57fe5b81526020019550505050505060405180910390f35b6102ab610a2a565b604080516001600160a01b039092168252519081900360200190f35b61036a600480360360208110156102dd57600080fd5b8101906020810181356401000000008111156102f857600080fd5b82018360208201111561030a57600080fd5b8035906020019184602083028401116401000000008311171561032c57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610a4e945050505050565b005b610374610b43565b60408051938452602084019290925282820152519081900360600190f35b61039a610bab565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156103d65781810151838201526020016103be565b505050509050019250505060405180910390f35b61036a6004803603606081101561040057600080fd5b5080359060208101359060400135610c72565b61036a6004803603604081101561042957600080fd5b5080359060200135610f63565b61045b6004803603604081101561044c57600080fd5b5080359060200135151561106f565b604051808060200180602001848152602001838103835286818151815260200191508051906020019060200280838360005b838110156104a557818101518382015260200161048d565b50505050905001838103825285818151815260200191508051906020019060200280838360005b838110156104e45781810151838201526020016104cc565b505050509050019550505050505060405180910390f35b61036a6004803603602081101561051157600080fd5b5035611431565b6105356004803603602081101561052e57600080fd5b5035611498565b60408051918252519081900360200190f35b61036a6114ab565b6102ab611501565b610535611581565b6105356004803603602081101561057557600080fd5b50356115a5565b6102ab6004803603602081101561059257600080fd5b50356115ba565b6105a16115e4565b604051808060200180602001878152602001868152602001858152602001848152602001838103835289818151815260200191508051906020019060200280838360005b838110156105fd5781810151838201526020016105e5565b50505050905001838103825288818151815260200191508051906020019060200280838360005b8381101561063c578181015183820152602001610624565b505050509050019850505050505050505060405180910390f35b61065e61194d565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610698578181015183820152602001610680565b50505050905090810190601f1680156106c55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61036a600480360360208110156106e957600080fd5b50356119da565b6102ab611a3d565b610535611a61565b610708611a6c565b6040805192835260208301919091528051918290030190f35b61036a6004803603608081101561073757600080fd5b506001600160a01b038135169060208101359060408101359060600135611a87565b6105356004803603604081101561076f57600080fd5b50803590602001356001600160a01b0316611ae3565b610535611b37565b610535600480360360208110156107a357600080fd5b5035611b5b565b6102ab600480360360208110156107c057600080fd5b5035611b66565b61036a600480360360208110156107dd57600080fd5b50356001600160a01b0316611b76565b610535611c7b565b6107fd611ca9565b6040518088815260200187815260200186815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019060200280838360008381101561063c578181015183820152602001610624565b610868611d4f565b60408051958652602086019490945284840192909252606084015215156080830152519081900360a00190f35b61036a600480360360208110156108ab57600080fd5b5035611df6565b610708611e6c565b610535611e87565b61036a600480360360408110156108d857600080fd5b50803590602001351515611eab565b610535600480360360408110156108fd57600080fd5b506001600160a01b0381351690602001356120ab565b61036a600480360360e081101561092957600080fd5b81359160208101359160408201359160608101359160808201359160a08101359181019060e0810160c082013564010000000081111561096857600080fd5b82018360208201111561097a57600080fd5b8035906020019184602083028401116401000000008311171561099c57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506120f4945050505050565b6102ab61222a565b60005460ff1681565b6002546004546001600160801b0380831693600160801b909304169160ff8083169261010081046001600160f01b031692600160f81b90910490911690565b7f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289481565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614610a8657610a8661224e565b6000815111610a9157fe5b600181511180610ac75750306001600160a01b031681600081518110610ab357fe5b60200260200101516001600160a01b031614155b610acd57fe5b8051610ae0906011906020840190614fa1565b5080516001600160401b0381118015610af857600080fd5b50604051908082528060200260200182016040528015610b22578160200160208202803683370190505b508051610b3791601091602090910190614fa1565b50610b406122b6565b50565b7f0000000000000000000000000000000000000000000000000000000062cf1e467f00000000000000000000000000000000000000000000000000000000000000b47f000000000000000000000000000000000000000000000000000000000000005a909192565b6010546060906001148015610bc257506011546001145b8015610bf85750306001600160a01b03166011600081548110610be157fe5b6000918252602090912001546001600160a01b0316145b610c5c576011805480602002602001604051908101604052809291908181526020018280548015610c5257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610c34575b5050505050610c6c565b6040805160008152602081019091525b90505b90565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614610caa57610caa61224e565b600054604080518082019091526016815275119514d3c8185b1c9958591e481858dd1a5d985d195960521b60208201529060ff1615610d675760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610d2c578181015183820152602001610d14565b50505050905090810190601f168015610d595780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f0000000000000000000000000000000000000000000000000000000062cf1e468414610e0a5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f00000000000000000000000000000000000000000000000000000000000000b48314610ead5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f000000000000000000000000000000000000000000000000000000000000005a8214610f505760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50506000805460ff191660011790555050565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614610f9b57610f9b61224e565b600054604080518082019091526016815275119514d3c8185b1c9958591e481858dd1a5d985d195960521b60208201529060ff161561101b5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5061102582612492565b600280546001600160801b0319166001600160801b039290921691909117905561104e81612492565b600280546001600160801b03928316600160801b0292169190911790555050565b6060806000336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289416146110ac576110ac61224e565b60006110b7866124da565b60018101805460ff60f01b191690556005810154909150600090156110ef578160050154612710836008015402816110eb57fe5b0490505b6001820154600160f81b900460ff168061110d575081600401548111155b15611180576001820154600160f81b900460ff1661116d57600482015460408051838152602081019290925242828201525188917fbece8aa526cdc5e528cdaa56c1d03edc19da51e41845aa146f64a7071f74c65a919081900360600190a25b61117987836000612614565b505061142a565b606080606061118e85612835565b91945092509050606061119f615002565b6111a985856129ca565b61012081015191935091507f00000000000000000000000000000000000000000000000000000000000005dc90611205906005908f907f00000000000000000000000000000000000000000000000000000000000000c8612ce8565b1115611223576112178c886000612614565b5050505050505061142a565b60108701805460ff199081166001908117909255610120830151600f8a01819055600280546001600160801b0319166001600160801b03928316178216600160801b429384160217905560048054600160f81b6001600160f81b036001600160f01b0390941661010002610100600160f81b031992909516909517169290921716919091179055600e870154156113405761130f87600e0180548060200260200160405190810160405280929190818152602001828054801561130557602002820191906000526020600020905b8154815260200190600101908083116112f1575b5050505050612d74565b60038054426001600160801b03908116600160801b029381166001600160801b031990921691909117169190911790555b60008b156113785760006113538e612e98565b90506113628984838789612f58565b8251929e50909c509a501561137657600191505b505b6113848d838584611add565b8c7ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3089600f0154838561014001518661016001518d60100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156113f557fe5b8152602001828152602001965050505050505060405180910390a2505050600190940180546001600160f81b03169055505050505b9250925092565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289416146114695761146961224e565b6000611474826124da565b6001808201805460ff60f01b191690559091506114949083908390612614565b5050565b60006114a38261320e565b90505b919050565b60005460ff166114bd576114bd613293565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289416146114f5576114f561224e565b6000805460ff19169055565b601054600090600114801561151857506011546001145b801561154e5750306001600160a01b0316601160008154811061153757fe5b6000918252602090912001546001600160a01b0316145b611559576000610c6c565b601060008154811061156757fe5b6000918252602090912001546001600160a01b0316905090565b7f000000000000000000000000000000000000000000000000000000000000000581565b60006115b0826132fd565b600f015492915050565b601181815481106115ca57600080fd5b6000918252602090912001546001600160a01b0316905081565b606080600080600080601080548060200260200160405190810160405280929190818152602001828054801561164357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611625575b50506007548451949a506001600160f01b031694506000939250506001600160401b0382119050801561167557600080fd5b5060405190808252806020026020018201604052801561169f578160200160208202803683370190505b509050600087516001600160401b03811180156116bb57600080fd5b506040519080825280602002602001820160405280156116e5578160200160208202803683370190505b50905060005b88518110156118745760006001600160a01b031689828151811061170b57fe5b60200260200101516001600160a01b031614156117295760006117ad565b88818151811061173557fe5b60200260200101516001600160a01b0316633e5aa26a856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561178057600080fd5b505afa158015611794573d6000803e3d6000fd5b505050506040513d60208110156117aa57600080fd5b50515b8382815181106117b957fe5b602002602001018181525050601181815481106117d257fe5b9060005260206000200160009054906101000a90046001600160a01b03166001600160a01b031663eb91d37e6040518163ffffffff1660e01b8152600401604080518083038186803b15801561182757600080fd5b505afa15801561183b573d6000803e3d6000fd5b505050506040513d604081101561185157600080fd5b5051825183908390811061186157fe5b60209081029190910101526001016116eb565b50600061188460058a85856133c5565b905061189260058a8361356b565b97507f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d6001600160a01b0316633e5aa26a856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156118f857600080fd5b505afa15801561190c573d6000803e3d6000fd5b505050506040513d602081101561192257600080fd5b5051965061193360058a858461369f565b9550611940600587613767565b9450505050909192939495565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156119d25780601f106119a7576101008083540402835291602001916119d2565b820191906000526020600020905b8154815290600101906020018083116119b557829003601f168201915b505050505081565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611a1257611a1261224e565b6000611a1d826124da565b6001808201805460ff60f01b1916905590915061149490839083906137bc565b7f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d81565b6000610c6c4261320e565b6003546001600160801b0380821691600160801b9004169091565b60005460ff16611a9957611a99613293565b336001600160a01b037f00000000000000000000000010000000000000000000000000000000000000031614611ad157611ad161224e565b611add848484846138f7565b50505050565b600080611aef846132fd565b90506000611afd8285613b94565b905080611b0f57600092505050611b31565b600019016000908152600d90910160205260409020546001600160801b031690505b92915050565b7f00000000000000000000000000000000000000000000000000000000000005dc81565b60006114a382612e98565b601081815481106115ca57600080fd5b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611bae57611bae61224e565b6040805160208101909152308152611bca906011906001614fa1565b5060408051602081019091526001600160a01b0382168152611bf0906010906001614fa1565b50806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015611c2a57600080fd5b505afa158015611c3e573d6000803e3d6000fd5b505050506040513d6020811015611c5457600080fd5b50516001600160a01b03909116600090815260066020526040902060ff909116600a0a9055565b600080611c86611a61565b905080611c97576000915050610c6f565b611ca360018203612e98565b91505090565b60008060008060008060606005600301546005600401546005800154600560060154600560070154600560080154600560090180805480602002602001604051908101604052809291908181526020018280548015611d3157602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611d13575b50505050509050965096509650965096509650965090919293949596565b6000806000806000611d5f611a61565b9450611d6a85613bec565b93507f000000000000000000000000000000000000000000000000000000000000005a8401925060006005817f00000000000000000000000000000000000000000000000000000000000000c88881611dbf57fe5b0681526020810191909152604001600020600101549596949593946001600160f01b03851694600160f81b900460ff169350915050565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611e2e57611e2e61224e565b438110611e3a57600080fd5b600160f01b8110611e4a57600080fd5b600780546001600160f01b0319166001600160f01b0392909216919091179055565b6002546001600160801b0380821691600160801b9004169091565b7f00000000000000000000000000000000000000000000000000000000000000c881565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611ee357611ee361224e565b6000611eed611a61565b905060006005817f00000000000000000000000000000000000000000000000000000000000000c88481611f1d57fe5b0681526020810191909152604001600090812060108101805460ff1916905560088101829055600c81018290556007546001820180546001600160f01b039092166001600160f01b0319909216919091176001600160f81b0316600160f81b87151502179055600281018490559150611f9a90600e830190615063565b8215611fa7575050611494565b606080611fb2613c39565b6007549194509250612062915084908890611ff7907f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d906001600160f01b0316613e2f565b601080548060200260200160405190810160405280929190818152602001828054801561204d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161202f575b5060059796959493508a925089915050613ec0565b837fc0eaa359541c7c642d9947c9496507c134f3e4f8e1fd433313eb27dc48cb1fb761208d86613bec565b604080519182524260208301528051918290030190a2505050505050565b60006120ed7f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d846120db856132fd565b600101546001600160f01b0316613f6f565b9392505050565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e02894161461212c5761212c61224e565b60088790556009869055600a859055600b849055600c839055600d829055600e5460005b818110156121ad5760006005600a0160006005600901848154811061217157fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055600101612150565b5050805160005b8181101561220b5760016005600a0160008584815181106121d157fe5b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff19169115159190911790556001016121b4565b50815161221f90600e906020850190614fa1565b505050505050505050565b7f000000000000000000000000100000000000000000000000000000000000000381565b604080518082018252600d81526c1058d8d95cdcc819195b9a5959609a1b6020808301918252925162461bcd60e51b81526004810193845282516024820152825192939283926044909201919080838360008315610d2c578181015183820152602001610d14565b60115460011480156122f25750306001600160a01b031660116000815481106122db57fe5b6000918252602090912001546001600160a01b0316145b156122fc57612490565b60005b601154811015610b405760006011828154811061231857fe5b6000918252602091829020015460408051635c222bad60e01b815290516001600160a01b0390921692635c222bad92600480840193829003018186803b15801561236157600080fd5b505afa158015612375573d6000803e3d6000fd5b505050506040513d602081101561238b57600080fd5b505160108054919250908390811061239f57fe5b6000918252602090912001546001600160a01b03828116911614156123c45750612488565b80601083815481106123d257fe5b600091825260209091200180546001600160a01b0319166001600160a01b0392831617905581161561248657806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561243757600080fd5b505afa15801561244b573d6000803e3d6000fd5b505050506040513d602081101561246157600080fd5b50516001600160a01b038216600090815260066020526040902060ff909116600a0a90555b505b6001016122ff565b565b6000600160801b82106124d65760405162461bcd60e51b81526004018080602001828103825260278152602001806151656027913960400191505060405180910390fd5b5090565b60006124e582614012565b4210156040518060400160405280602081526020017f45706f6368206e6f7420726561647920666f722066696e616c697a6174696f6e8152509061256a5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50612574826132fd565b90506000601082015460ff16600581111561258b57fe5b146040518060400160405280601781526020017f45706f636820616c72656164792066696e616c697a65640000000000000000008152509061260e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50919050565b600e820154156128255761267782600e0180548060200260200160405190810160405280929190818152602001828054801561130557602002820191906000526020600020908154815260200190600101908083116112f1575050505050612d74565b600f8301558061268857600261268b565b60045b60108301805460ff191660018360058111156126a357fe5b0217905550600f82015460028054426001600160801b03908116600160801b029381166001600160801b0319909216919091171691909117905560108201546004805460ff9092169160ff191660018360058111156126fe57fe5b021790555060048054610100600160f81b031916610100426001600160f01b03160217808255601084015460ff1691906001600160f81b0316600160f81b83600581111561274857fe5b0217905550600f82015460038054426001600160801b03908116600160801b029381166001600160801b0319909216919091171691909117905561278b83610b40565b827ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3083600f015460008060008760100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156127f457fe5b8152602001828152602001965050505050505060405180910390a26001820180546001600160f81b03169055612830565b6128308383836137bc565b505050565b6060806060600084600c01549050806001600160401b038111801561285957600080fd5b50604051908082528060200260200182016040528015612883578160200160208202803683370190505b509350806001600160401b038111801561289c57600080fd5b506040519080825280602002602001820160405280156128c6578160200160208202803683370190505b5091506000816001600160401b03811180156128e157600080fd5b5060405190808252806020026020018201604052801561290b578160200160208202803683370190505b50905060005b828110156129b3576000818152600d880160205260409020805487516001600160801b039091169088908490811061294557fe5b602090810291909101015280548551600160801b9091046001600160401b03169086908490811061297257fe5b602090810291909101015280548351600160c01b9091046001600160401b03169084908490811061299f57fe5b602090810291909101015250600101612911565b506129bf868483614045565b935050509193909250565b60606129d4615002565b8351806001600160401b03811180156129ec57600080fd5b50604051908082528060200260200182016040528015612a16578160200160208202803683370190505b50925060005b81811015612a445780848281518110612a3157fe5b6020908102919091010152600101612a1c565b50612a5b6002600060018403600080888b8b61415c565b608085015260608401528083528351859185918110612a7657fe5b602002602001015181518110612a8857fe5b602090810291909101015160a0830181905260808301516060840151845192010190612ac1578251600060c08501526020840152612b15565b60048104836060015111612ae5578251606084015160c08501526020840152612b15565b612b0960016000600186600001510360008760a00151886080015101898c8c61415c565b5060c085015260208401525b825160001983011415612b3657825160006101008501526040840152612b8e565b60048104836080015111612b5b57825160808401516101008501526040840152612b8e565b612b8160038460000151600101600185038660a001518760600151016000898c8c61415c565b6101008601525060408401525b8584846000015181518110612b9f57fe5b602002602001015181518110612bb157fe5b6020908102919091010151610120840152600281048360a00151846060015101148015612bdf575060028106155b15612c0f576002612bf9846000015160018503878a614437565b8461012001510181612c0757fe5b046101208401525b612c29836020015160006000198660c00151888b8b6144f7565b60c085015260208401526040830151610100840151612c549190600019850190600190888b8b6144f7565b610100850152604084015260208301518451879186918110612c7257fe5b602002602001015181518110612c8457fe5b6020026020010151836101400181815250508584846040015181518110612ca757fe5b602002602001015181518110612cb957fe5b602090810291909101015161016084015261010083015160c08401519091030360e08301525090939092509050565b600083612cf757506000612d6c565b6000858184600019880181612d0857fe5b068152602001908152602001600020600f0154905080841415612d2f576000915050612d6c565b83612d425764e8d4a51000915050612d6c565b600084821115612d555750838103612d5a565b508084035b612d6781612710876145cb565b925050505b949350505050565b805160009080612d8057fe5b60015b81811015612e2c576000848281518110612d9957fe5b6020026020010151905060008290505b600081118015612dce575081866001830381518110612dc457fe5b6020026020010151115b15612e0a57856001820381518110612de257fe5b6020026020010151868281518110612df657fe5b602090810291909101015260001901612da9565b81868281518110612e1757fe5b60209081029190910101525050600101612d83565b506002810460018083161415612e5957838181518110612e4857fe5b6020026020010151925050506114a6565b6002848281518110612e6757fe5b6020026020010151856001840381518110612e7e57fe5b60200260200101510181612e8e57fe5b04925050506114a6565b60007f00000000000000000000000010000000000000000000000000000000000000036001600160a01b031663cd4b6914836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015612efe57600080fd5b505afa158015612f12573d6000803e3d6000fd5b505050506040513d6020811015612f2857600080fd5b50516040805160208082019390935230818301528151808203830181526060909101909152805191012092915050565b6020840151606090819060009081905b88604001518111613033576000878281518110612f8157fe5b602002602001015190506000878281518110612f9957fe5b60200260200101511115613029576000818152600d8c0160205260409020546101408b01516001600160801b0390911690811480612fe457508a6101600151816001600160801b0316145b801561301557506000828152600d8d016020526040902060010154613013908b906001600160a01b03166146d7565b155b1561302157505061302b565b506001909201915b505b600101612f68565b50806001600160401b038111801561304a57600080fd5b50604051908082528060200260200182016040528015613074578160200160208202803683370190505b509350806001600160401b038111801561308d57600080fd5b506040519080825280602002602001820160405280156130b7578160200160208202803683370190505b5060208901519093506000905b896040015181116132005760008882815181106130dd57fe5b6020026020010151905060008882815181106130f557fe5b6020026020010151905060008111156131f5576000828152600d8e0160205260409020546101408d01516001600160801b039091169081148061314557508c6101600151816001600160801b0316145b801561317657506000838152600d8f016020526040902060010154613174908d906001600160a01b03166146d7565b155b15613183575050506131f8565b6000838152600d8f01602052604090206001015489516001600160a01b03909116908a90879081106131b157fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818886815181106131de57fe5b602090810291909101015250948501946001909301925b50505b6001016130c4565b505050955095509592505050565b60007f0000000000000000000000000000000000000000000000000000000062cf1e46821015613240575060006114a6565b7f00000000000000000000000000000000000000000000000000000000000000b47f0000000000000000000000000000000000000000000000000000000062cf1e4683038161328b57fe5b0490506114a6565b604080518082018252600f81526e4654534f206e6f742061637469766560881b6020808301918252925162461bcd60e51b81526004810193845282516024820152825192939283926044909201919080838360008315610d2c578181015183820152602001610d14565b60006005817f00000000000000000000000000000000000000000000000000000000000000c8848161332b57fe5b0681526020019081526020016000209050806002015482146040518060400160405280601881526020017f45706f63682064617461206e6f7420617661696c61626c6500000000000000008152509061260e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b82516060906000816001600160401b03811180156133e257600080fd5b5060405190808252806020026020018201604052801561340c578160200160208202803683370190505b5090506000805b838110156134f55760006001600160a01b031688828151811061343257fe5b60200260200101516001600160a01b0316141561344e576134ed565b60006134c587838151811061345f57fe5b60200260200101518b60010160008c868151811061347957fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020548a85815181106134ae57fe5b60200260200101516145cb9092919063ffffffff16565b9050808483815181106134d457fe5b60209081029190910101526134e98382614712565b9250505b600101613413565b5080156135605760005b8381101561355e5761353f61353261271088848151811061351c57fe5b602002602001015161476c90919063ffffffff16565b838584815181106134ae57fe5b83828151811061354b57fe5b60209081029190910101526001016134ff565b505b509695505050505050565b8151606090806001600160401b038111801561358657600080fd5b506040519080825280602002602001820160405280156135b0578160200160208202803683370190505b50915060005b818110156136965760006001600160a01b03168582815181106135d557fe5b60200260200101516001600160a01b0316146136735760008660010160008784815181106135ff57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002054905061365561271064e8d4a510008161363d57fe5b04670de0b6b3a764000002828785815181106134ae57fe5b84838151811061366157fe5b6020026020010181815250505061368e565b600083828151811061368157fe5b6020026020010181815250505b6001016135b6565b50509392505050565b600080805b855181101561375d5760006001600160a01b03168682815181106136c457fe5b60200260200101516001600160a01b03161461375557600061271061373d8784815181106136ee57fe5b60200260200101518a60010160008b878151811061370857fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020548886815181106134ae57fe5b8161374457fe5b0490506137518382614712565b9250505b6001016136a4565b5095945050505050565b600080836005015483101561377e575060006120ed565b8360060154831061379257506113886120ed565b600584015460068501546101f4919081900390850361119402816137b257fe5b0401949350505050565b82156137d8576002546001600160801b0316600f8301556137e0565b6000600f8301555b806137ec5760036137ef565b60055b60108301805460ff1916600183600581111561380757fe5b021790555060048054610100600160f81b031916610100426001600160f01b03160217808255601084015460ff1691906001600160f81b0316600160f81b83600581111561385157fe5b021790555061385f83610b40565b827ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3083600f015460008060008760100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156138c857fe5b8152602001828152602001965050505050505060405180910390a25060010180546001600160f81b0316905550565b60408051808201909152600e81526d0a0e4d2c6ca40e8dede40d0d2ced60931b6020820152600160801b831061396e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50613978836147c5565b6040518060400160405280601881526020017f52657665616c20706572696f64206e6f74206163746976650000000000000000815250906139fa5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060006005817f00000000000000000000000000000000000000000000000000000000000000c88681613a2957fe5b0681526020810191909152604001600020600181015490915060ff600160f81b8204811691600160f01b8104909116906001600160f01b03168180613a8e5750828015613a8e57506001600160a01b0388166000908152600f602052604090205460ff165b6040518060400160405280602081526020017f45706f6368206e6f7420696e697469616c697a656420666f722072657665616c81525090613b105760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50600080613b21868b898887614808565b9092509050613b356005878c85858d614868565b604080518981524260208201528082018490526060810183905290518a916001600160a01b038d16917fc1b1d37612887c207efe8cb44f4069b7f10c45edaf6e8405648a94e02b6e9ec79181900360800190a350505050505050505050565b600c820154600090815b81811015613be1576000818152600d860160205260409020600101546001600160a01b0385811691161415613bd9576001019150611b319050565b600101613b9e565b506000949350505050565b7f0000000000000000000000000000000000000000000000000000000062cf1e46600182017f00000000000000000000000000000000000000000000000000000000000000b40201919050565b6060806060613c466122b6565b6010805480602002602001604051908101604052809291908181526020018280548015613c9c57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613c7e575b5050505050925082516001600160401b0381118015613cba57600080fd5b50604051908082528060200260200182016040528015613ce4578160200160208202803683370190505b50915082516001600160401b0381118015613cfe57600080fd5b50604051908082528060200260200182016040528015613d28578160200160208202803683370190505b50905060005b8351811015613e2957613d62848281518110613d4657fe5b60209081029190910101516007546001600160f01b0316613e2f565b838281518110613d6e57fe5b60200260200101818152505060118181548110613d8757fe5b9060005260206000200160009054906101000a90046001600160a01b03166001600160a01b031663eb91d37e6040518163ffffffff1660e01b8152600401604080518083038186803b158015613ddc57600080fd5b505afa158015613df0573d6000803e3d6000fd5b505050506040513d6040811015613e0657600080fd5b50518251839083908110613e1657fe5b6020908102919091010152600101613d2e565b50909192565b60006001600160a01b038316613e4757506000611b31565b826001600160a01b031663caeb942b836040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b158015613e8d57600080fd5b505af1158015613ea1573d6000803e3d6000fd5b505050506040513d6020811015613eb757600080fd5b50519050611b31565b613ecd87878585856149b8565b6007870154600380880191909155600888015460048801556005870186905560068701859055870154613f08908581613f0257fe5b04612492565b86546001600160801b0319166001600160801b039190911617865560048701546007870154613f39919081613f0257fe5b86546001600160801b03918216600160801b0291161786555050506001909201805460ff60f01b1916600160f01b179055505050565b60006001600160a01b038416613f87575060006120ed565b836001600160a01b031663e587497e84846040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015613fde57600080fd5b505af1158015613ff2573d6000803e3d6000fd5b505050506040513d602081101561400857600080fd5b5051949350505050565b60007f000000000000000000000000000000000000000000000000000000000000005a61403e83613bec565b0192915050565b600c830154606090806001600160401b038111801561406357600080fd5b5060405190808252806020026020018201604052801561408d578160200160208202803683370190505b509150600061409b85614a21565b905060006140a885614a21565b90506000806140b8898585614a66565b905083156140c857806127100391505b60005b8581101561414f57600083156141115761410e8a83815181106140ea57fe5b602002602001015164e8d4a51000026127108802866145cb9092919063ffffffff16565b90505b6000831561412b576141288a84815181106140ea57fe5b90505b80820189848151811061413a57fe5b602090810291909101015250506001016140cb565b5050505050509392505050565b6000806000888a1415614176575088915086905085614429565b61417e615081565b888152602081018890526141906150be565b60208082018d905260408083018d90528051448184015242818301528151808203830181526060909101909152805191012060005b602083015160408401516141f791908160018183030186816141e357fe5b0601876000015188602001518f8f8f614acd565b606087015260408601528084528a518b9190811061421157fe5b60200260200101518360600181815250508783606001518151811061423257fe5b6020026020010151846080018181525050836060015184604001518560800151010190508e6002141561430d5760028106600282040160a0850181905280820360c086015260408501511080159061428d57508b8460a00151115b156142b257825160001901604084015260608401516080850151016020850152614308565b8360c0015184606001511180156142cc57508a8460c00151115b156142ed578251600101602084015260408401516080850151018452614308565b50505160408201516060909201519094509092509050614429565b614424565b8e6001141561439e576004810460a0850181905280820360c0860152604085015111801561433e57508b8460a00151115b1561436357825160001901604084015260608401516080850151016020850152614308565b8360c001518460600151101580156142cc57508a8460c0015111156142ed578251600101602084015260408401516080850151018452614308565b6004810460c08501819052810360a085018190526040850151108015906143c857508b8460a00151115b156143ed57825160001901604084015260608401516080850151016020850152614424565b8360c00151846060015111801561440757508a8460c00151115b156142ed5782516001016020840152604084015160808501510184525b6141c5565b985098509895505050505050565b60008385141561446f578183868151811061444e57fe5b60200260200101518151811061446057fe5b60200260200101519050612d6c565b60008284876001018151811061448157fe5b60200260200101518151811061449357fe5b602002602001015190506000808760020190505b8681116144eb57848682815181106144bb57fe5b6020026020010151815181106144cd57fe5b60200260200101519150828210156144e3578192505b6001016144a7565b50909695505050505050565b60008085888a038802821361451257898792509250506145bf565b600085878c8151811061452157fe5b60200260200101518151811061453357fe5b602090810291909101015190508a89016000815b60008c8e830302136145b35789818151811061455f57fe5b602002602001015191508389838151811061457657fe5b602002602001015114156145ac5787828151811061459057fe5b6020026020010151850394506145a783828c614c96565b918b01915b8b01614547565b50508990039350909150505b97509795505050505050565b6000808211614614576040805162461bcd60e51b815260206004820152601060248201526f4469766973696f6e206279207a65726f60801b604482015290519081900360640190fd5b83614621575060006120ed565b8383028385828161462e57fe5b0414156146475782818161463e57fe5b049150506120ed565b600083868161465257fe5b049050600084878161466057fe5b069050600085878161466e57fe5b049050600086888161467c57fe5b0690506146ca61469688614690868561476c565b90614cfe565b6146c46146a3868661476c565b6146c46146b0898761476c565b6146c48d6146be8c8b61476c565b9061476c565b90614712565b9998505050505050505050565b604080516020808201949094526001600160a01b03929092168282015280518083038201815260609092019052805191012060019081161490565b6000828201838110156120ed576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60008261477b57506000611b31565b8282028284828161478857fe5b04146120ed5760405162461bcd60e51b815260040180806020018281038252602181526020018061518c6021913960400191505060405180910390fd5b6000806147d183613bec565b90504281111580156120ed57507f000000000000000000000000000000000000000000000000000000000000005a01421092915050565b600080831561481c5750600090508061485e565b84915061482a878785614d65565b87549091506001600160801b0380821691600160801b9004168184111561484f578193505b8083111561485b578092505b50505b9550959350505050565b600085600c0154905060006148898686868a600601548b6007015488614e23565b63ffffffff838116608083019081526000858152600d8b0160209081526040918290208551815492870151938701516001600160801b03199093166001600160801b039091161767ffffffffffffffff60801b1916600160801b6001600160401b0394851602176001600160c01b0316600160c01b939092169290920217815560608401516001918201805493516001600160a01b03199094166001600160a01b039092169190911763ffffffff60a01b1916600160a01b93851693909302929092179091558401600c8a0155600889015491925061496b9190879061471216565b60088801556001600160a01b0386166000908152600a8901602052604090205460ff16156149ae57600e8701805460018101825560009182526020909120018390555b5050505050505050565b82516149cd90600a8601906020860190614fa1565b506149da858484846133c5565b80516149f091600b8701916020909101906150e6565b5060006149fe868685614e80565b600786018190559050614a118682613767565b8560090181905550505050505050565b600080805b8351811015614a5f57614a55848281518110614a3e57fe5b60200260200101518361471290919063ffffffff16565b9150600101614a26565b5092915050565b600081614a75575060006120ed565b82614a8357506127106120ed565b6000614a978361271064e8d4a510006145cb565b905084600301548110614ab057505060098301546120ed565b60038501546009860154614ac59183906145cb565b9150506120ed565b60008060008085878b81518110614ae057fe5b602002602001015181518110614af257fe5b60200260200101519050600060026001600160401b0381118015614b1557600080fd5b50604051908082528060200260200182016040528015614b3f578160200160208202803683370190505b5090508981600081518110614b5057fe5b6020026020010181815250508881600181518110614b6a57fe5b60209081029190910101528c8c614b828d828c614c96565b81805b82811015614c465760008c8281518110614b9b57fe5b60200260200101519050868c8281518110614bb257fe5b60200260200101511015614c09578a8181518110614bcc57fe5b602002602001015186600081518110614be157fe5b602002602001018181510191508181525050614bfe83838f614c96565b600190920191614c3d565b8a8181518110614c1557fe5b602002602001015186600181518110614c2a57fe5b6020026020010181815101915081815250505b50600101614b85565b50614c5282828d614c96565b8084600081518110614c6057fe5b602002602001015185600181518110614c7557fe5b60200260200101519750975097505050505050985098509895505050505050565b81831415614ca357612830565b808281518110614caf57fe5b6020026020010151818481518110614cc357fe5b6020026020010151828581518110614cd757fe5b60200260200101838581518110614cea57fe5b602090810291909101019190915252505050565b6000808211614d54576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381614d5d57fe5b049392505050565b600a83015460009081906001600160401b0381118015614d8457600080fd5b50604051908082528060200260200182016040528015614dae578160200160208202803683370190505b50905060005b600a860154811015614e0d57614dee86600a018281548110614dd257fe5b6000918252602090912001546001600160a01b03168686613f6f565b828281518110614dfa57fe5b6020908102919091010152600101614db4565b50614e1a60058683614e80565b95945050505050565b614e2b615121565b6001600160a01b0387166060820152614e448685614f70565b6001600160401b03166020820152614e5c8584614f70565b6001600160401b031660408201526001600160801b03909116815295945050505050565b600080805b600a850154811015614f675760006001600160a01b031685600a018281548110614eab57fe5b6000918252602090912001546001600160a01b03161415614ecb57614f5f565b614f5c612710614f4c868481518110614ee057fe5b60200260200101518960010160008a600a018781548110614efd57fe5b60009182526020808320909101546001600160a01b03168352820192909252604001902054600b8a01805487908110614f3257fe5b90600052602060002001546145cb9092919063ffffffff16565b81614f5357fe5b84919004614712565b91505b600101614e85565b50949350505050565b6000811580614f7d575082155b15614f8a57506000611b31565b614f9a8364e8d4a51000846145cb565b9050611b31565b828054828255906000526020600020908101928215614ff6579160200282015b82811115614ff657825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614fc1565b506124d692915061514f565b6040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b5080546000825590600052602060002090810190610b40919061514f565b6040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215614ff6579160200282015b82811115614ff6578251825591602001919060010190615106565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b5b808211156124d6576000815560010161515056fe53616665436173743a2076616c756520646f65736e27742066697420696e203132382062697473536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220a0feb28364f05773bf3bdc810cf6412e41f6fe7ef2c4a4adf535c3c83494e52564736f6c63430007060033