Contract Address Details

0x95362265Ff0D4d3c1D89C3D66B2BCE18aEa4A000

Contract Name
Ftso
Creator
0x4598a6–d10d29 at 0x2e0969–5035d1
Balance
0 FLR
Tokens
Fetching tokens...
Transactions
0 Transactions
Transfers
0 Transfers
Gas Used
Fetching gas used...
Last Balance Update
3077804
Contract name:
Ftso




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




Optimization runs
200
Verified at
2022-07-13T21:49:33.014701Z

Constructor Arguments

0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000500000000000000000000000010000000000000000000000000000000000000030000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028940000000000000000000000000000000000000000000000000000000062cf1e4600000000000000000000000000000000000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc00000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000

Arg [0] (string) : BTC
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/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/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/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"}]}]
            

Deployed ByteCode

0x608060405234801561001057600080fd5b50600436106102325760003560e01c80639edbf00711610130578063d89601fd116100b8578063f025bf661161007c578063f025bf66146108ba578063f670ebe3146108c2578063f72cab28146108e7578063f7dba1f514610913578063f937d6ad146109da57610232565b8063d89601fd146107ed578063e3749e0c146107f5578063e3b3a3b314610860578063e536f39614610895578063eb91d37e146108b257610232565b8063c5d8b9e7116100ff578063c5d8b9e714610759578063cc245ab514610785578063cd4b69141461078d578063cf35bdd0146107aa578063d0d552dd146107c757610232565b80639edbf007146106f0578063a29a839f146106f8578063af52df0814610700578063c1f6c36e1461072157610232565b80634afd5102116101be5780637d1d6f12116101825780637d1d6f121461055f578063826cc76b1461057c5780638357d08c1461059957806395d89b4114610656578063974d7a6b146106d357610232565b80634afd5102146104fb5780635303548b14610518578063555989da146105475780635c222bad1461054f5780636b52b2421461055757610232565b8063144e159111610205578063144e15911461036c57806318931c35146103925780632f0a6f3c146103ea578063306ba2531461041357806340462a2d1461043657610232565b806302fb0c5e14610237578063040d73b81461025357806311a7aaaa146102a3578063131fdee2146102c7575b600080fd5b61023f6109e2565b604080519115158252519081900360200190f35b61025b6109eb565b6040518086815260200185815260200184600581111561027757fe5b815260200183815260200182600581111561028e57fe5b81526020019550505050505060405180910390f35b6102ab610a2a565b604080516001600160a01b039092168252519081900360200190f35b61036a600480360360208110156102dd57600080fd5b8101906020810181356401000000008111156102f857600080fd5b82018360208201111561030a57600080fd5b8035906020019184602083028401116401000000008311171561032c57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610a4e945050505050565b005b610374610b43565b60408051938452602084019290925282820152519081900360600190f35b61039a610bab565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156103d65781810151838201526020016103be565b505050509050019250505060405180910390f35b61036a6004803603606081101561040057600080fd5b5080359060208101359060400135610c72565b61036a6004803603604081101561042957600080fd5b5080359060200135610f63565b61045b6004803603604081101561044c57600080fd5b5080359060200135151561106f565b604051808060200180602001848152602001838103835286818151815260200191508051906020019060200280838360005b838110156104a557818101518382015260200161048d565b50505050905001838103825285818151815260200191508051906020019060200280838360005b838110156104e45781810151838201526020016104cc565b505050509050019550505050505060405180910390f35b61036a6004803603602081101561051157600080fd5b5035611431565b6105356004803603602081101561052e57600080fd5b5035611498565b60408051918252519081900360200190f35b61036a6114ab565b6102ab611501565b610535611581565b6105356004803603602081101561057557600080fd5b50356115a5565b6102ab6004803603602081101561059257600080fd5b50356115ba565b6105a16115e4565b604051808060200180602001878152602001868152602001858152602001848152602001838103835289818151815260200191508051906020019060200280838360005b838110156105fd5781810151838201526020016105e5565b50505050905001838103825288818151815260200191508051906020019060200280838360005b8381101561063c578181015183820152602001610624565b505050509050019850505050505050505060405180910390f35b61065e61194d565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610698578181015183820152602001610680565b50505050905090810190601f1680156106c55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61036a600480360360208110156106e957600080fd5b50356119da565b6102ab611a3d565b610535611a61565b610708611a6c565b6040805192835260208301919091528051918290030190f35b61036a6004803603608081101561073757600080fd5b506001600160a01b038135169060208101359060408101359060600135611a87565b6105356004803603604081101561076f57600080fd5b50803590602001356001600160a01b0316611ae3565b610535611b37565b610535600480360360208110156107a357600080fd5b5035611b5b565b6102ab600480360360208110156107c057600080fd5b5035611b66565b61036a600480360360208110156107dd57600080fd5b50356001600160a01b0316611b76565b610535611c7b565b6107fd611ca9565b6040518088815260200187815260200186815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019060200280838360008381101561063c578181015183820152602001610624565b610868611d4f565b60408051958652602086019490945284840192909252606084015215156080830152519081900360a00190f35b61036a600480360360208110156108ab57600080fd5b5035611df6565b610708611e6c565b610535611e87565b61036a600480360360408110156108d857600080fd5b50803590602001351515611eab565b610535600480360360408110156108fd57600080fd5b506001600160a01b0381351690602001356120ab565b61036a600480360360e081101561092957600080fd5b81359160208101359160408201359160608101359160808201359160a08101359181019060e0810160c082013564010000000081111561096857600080fd5b82018360208201111561097a57600080fd5b8035906020019184602083028401116401000000008311171561099c57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506120f4945050505050565b6102ab61222a565b60005460ff1681565b6002546004546001600160801b0380831693600160801b909304169160ff8083169261010081046001600160f01b031692600160f81b90910490911690565b7f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289481565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614610a8657610a8661224e565b6000815111610a9157fe5b600181511180610ac75750306001600160a01b031681600081518110610ab357fe5b60200260200101516001600160a01b031614155b610acd57fe5b8051610ae0906011906020840190614fa1565b5080516001600160401b0381118015610af857600080fd5b50604051908082528060200260200182016040528015610b22578160200160208202803683370190505b508051610b3791601091602090910190614fa1565b50610b406122b6565b50565b7f0000000000000000000000000000000000000000000000000000000062cf1e467f00000000000000000000000000000000000000000000000000000000000000b47f000000000000000000000000000000000000000000000000000000000000005a909192565b6010546060906001148015610bc257506011546001145b8015610bf85750306001600160a01b03166011600081548110610be157fe5b6000918252602090912001546001600160a01b0316145b610c5c576011805480602002602001604051908101604052809291908181526020018280548015610c5257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610c34575b5050505050610c6c565b6040805160008152602081019091525b90505b90565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614610caa57610caa61224e565b600054604080518082019091526016815275119514d3c8185b1c9958591e481858dd1a5d985d195960521b60208201529060ff1615610d675760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610d2c578181015183820152602001610d14565b50505050905090810190601f168015610d595780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f0000000000000000000000000000000000000000000000000000000062cf1e468414610e0a5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f00000000000000000000000000000000000000000000000000000000000000b48314610ead5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060408051808201909152601e81527f496e76616c69642070726963652065706f636820706172616d6574657273000060208201527f000000000000000000000000000000000000000000000000000000000000005a8214610f505760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50506000805460ff191660011790555050565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614610f9b57610f9b61224e565b600054604080518082019091526016815275119514d3c8185b1c9958591e481858dd1a5d985d195960521b60208201529060ff161561101b5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5061102582612492565b600280546001600160801b0319166001600160801b039290921691909117905561104e81612492565b600280546001600160801b03928316600160801b0292169190911790555050565b6060806000336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289416146110ac576110ac61224e565b60006110b7866124da565b60018101805460ff60f01b191690556005810154909150600090156110ef578160050154612710836008015402816110eb57fe5b0490505b6001820154600160f81b900460ff168061110d575081600401548111155b15611180576001820154600160f81b900460ff1661116d57600482015460408051838152602081019290925242828201525188917fbece8aa526cdc5e528cdaa56c1d03edc19da51e41845aa146f64a7071f74c65a919081900360600190a25b61117987836000612614565b505061142a565b606080606061118e85612835565b91945092509050606061119f615002565b6111a985856129ca565b61012081015191935091507f00000000000000000000000000000000000000000000000000000000000005dc90611205906005908f907f00000000000000000000000000000000000000000000000000000000000000c8612ce8565b1115611223576112178c886000612614565b5050505050505061142a565b60108701805460ff199081166001908117909255610120830151600f8a01819055600280546001600160801b0319166001600160801b03928316178216600160801b429384160217905560048054600160f81b6001600160f81b036001600160f01b0390941661010002610100600160f81b031992909516909517169290921716919091179055600e870154156113405761130f87600e0180548060200260200160405190810160405280929190818152602001828054801561130557602002820191906000526020600020905b8154815260200190600101908083116112f1575b5050505050612d74565b60038054426001600160801b03908116600160801b029381166001600160801b031990921691909117169190911790555b60008b156113785760006113538e612e98565b90506113628984838789612f58565b8251929e50909c509a501561137657600191505b505b6113848d838584611add565b8c7ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3089600f0154838561014001518661016001518d60100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156113f557fe5b8152602001828152602001965050505050505060405180910390a2505050600190940180546001600160f81b03169055505050505b9250925092565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289416146114695761146961224e565b6000611474826124da565b6001808201805460ff60f01b191690559091506114949083908390612614565b5050565b60006114a38261320e565b90505b919050565b60005460ff166114bd576114bd613293565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e0289416146114f5576114f561224e565b6000805460ff19169055565b601054600090600114801561151857506011546001145b801561154e5750306001600160a01b0316601160008154811061153757fe5b6000918252602090912001546001600160a01b0316145b611559576000610c6c565b601060008154811061156757fe5b6000918252602090912001546001600160a01b0316905090565b7f000000000000000000000000000000000000000000000000000000000000000581565b60006115b0826132fd565b600f015492915050565b601181815481106115ca57600080fd5b6000918252602090912001546001600160a01b0316905081565b606080600080600080601080548060200260200160405190810160405280929190818152602001828054801561164357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611625575b50506007548451949a506001600160f01b031694506000939250506001600160401b0382119050801561167557600080fd5b5060405190808252806020026020018201604052801561169f578160200160208202803683370190505b509050600087516001600160401b03811180156116bb57600080fd5b506040519080825280602002602001820160405280156116e5578160200160208202803683370190505b50905060005b88518110156118745760006001600160a01b031689828151811061170b57fe5b60200260200101516001600160a01b031614156117295760006117ad565b88818151811061173557fe5b60200260200101516001600160a01b0316633e5aa26a856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561178057600080fd5b505afa158015611794573d6000803e3d6000fd5b505050506040513d60208110156117aa57600080fd5b50515b8382815181106117b957fe5b602002602001018181525050601181815481106117d257fe5b9060005260206000200160009054906101000a90046001600160a01b03166001600160a01b031663eb91d37e6040518163ffffffff1660e01b8152600401604080518083038186803b15801561182757600080fd5b505afa15801561183b573d6000803e3d6000fd5b505050506040513d604081101561185157600080fd5b5051825183908390811061186157fe5b60209081029190910101526001016116eb565b50600061188460058a85856133c5565b905061189260058a8361356b565b97507f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d6001600160a01b0316633e5aa26a856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b1580156118f857600080fd5b505afa15801561190c573d6000803e3d6000fd5b505050506040513d602081101561192257600080fd5b5051965061193360058a858461369f565b9550611940600587613767565b9450505050909192939495565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156119d25780601f106119a7576101008083540402835291602001916119d2565b820191906000526020600020905b8154815290600101906020018083116119b557829003601f168201915b505050505081565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611a1257611a1261224e565b6000611a1d826124da565b6001808201805460ff60f01b1916905590915061149490839083906137bc565b7f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d81565b6000610c6c4261320e565b6003546001600160801b0380821691600160801b9004169091565b60005460ff16611a9957611a99613293565b336001600160a01b037f00000000000000000000000010000000000000000000000000000000000000031614611ad157611ad161224e565b611add848484846138f7565b50505050565b600080611aef846132fd565b90506000611afd8285613b94565b905080611b0f57600092505050611b31565b600019016000908152600d90910160205260409020546001600160801b031690505b92915050565b7f00000000000000000000000000000000000000000000000000000000000005dc81565b60006114a382612e98565b601081815481106115ca57600080fd5b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611bae57611bae61224e565b6040805160208101909152308152611bca906011906001614fa1565b5060408051602081019091526001600160a01b0382168152611bf0906010906001614fa1565b50806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015611c2a57600080fd5b505afa158015611c3e573d6000803e3d6000fd5b505050506040513d6020811015611c5457600080fd5b50516001600160a01b03909116600090815260066020526040902060ff909116600a0a9055565b600080611c86611a61565b905080611c97576000915050610c6f565b611ca360018203612e98565b91505090565b60008060008060008060606005600301546005600401546005800154600560060154600560070154600560080154600560090180805480602002602001604051908101604052809291908181526020018280548015611d3157602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611d13575b50505050509050965096509650965096509650965090919293949596565b6000806000806000611d5f611a61565b9450611d6a85613bec565b93507f000000000000000000000000000000000000000000000000000000000000005a8401925060006005817f00000000000000000000000000000000000000000000000000000000000000c88881611dbf57fe5b0681526020810191909152604001600020600101549596949593946001600160f01b03851694600160f81b900460ff169350915050565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611e2e57611e2e61224e565b438110611e3a57600080fd5b600160f01b8110611e4a57600080fd5b600780546001600160f01b0319166001600160f01b0392909216919091179055565b6002546001600160801b0380821691600160801b9004169091565b7f00000000000000000000000000000000000000000000000000000000000000c881565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e028941614611ee357611ee361224e565b6000611eed611a61565b905060006005817f00000000000000000000000000000000000000000000000000000000000000c88481611f1d57fe5b0681526020810191909152604001600090812060108101805460ff1916905560088101829055600c81018290556007546001820180546001600160f01b039092166001600160f01b0319909216919091176001600160f81b0316600160f81b87151502179055600281018490559150611f9a90600e830190615063565b8215611fa7575050611494565b606080611fb2613c39565b6007549194509250612062915084908890611ff7907f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d906001600160f01b0316613e2f565b601080548060200260200160405190810160405280929190818152602001828054801561204d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161202f575b5060059796959493508a925089915050613ec0565b837fc0eaa359541c7c642d9947c9496507c134f3e4f8e1fd433313eb27dc48cb1fb761208d86613bec565b604080519182524260208301528051918290030190a2505050505050565b60006120ed7f0000000000000000000000001d80c49bbbcd1c0911346656b529df9e5c2f783d846120db856132fd565b600101546001600160f01b0316613f6f565b9392505050565b336001600160a01b037f000000000000000000000000ab7c7da1ff2b25d0867908ddfa52827570e02894161461212c5761212c61224e565b60088790556009869055600a859055600b849055600c839055600d829055600e5460005b818110156121ad5760006005600a0160006005600901848154811061217157fe5b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055600101612150565b5050805160005b8181101561220b5760016005600a0160008584815181106121d157fe5b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff19169115159190911790556001016121b4565b50815161221f90600e906020850190614fa1565b505050505050505050565b7f000000000000000000000000100000000000000000000000000000000000000381565b604080518082018252600d81526c1058d8d95cdcc819195b9a5959609a1b6020808301918252925162461bcd60e51b81526004810193845282516024820152825192939283926044909201919080838360008315610d2c578181015183820152602001610d14565b60115460011480156122f25750306001600160a01b031660116000815481106122db57fe5b6000918252602090912001546001600160a01b0316145b156122fc57612490565b60005b601154811015610b405760006011828154811061231857fe5b6000918252602091829020015460408051635c222bad60e01b815290516001600160a01b0390921692635c222bad92600480840193829003018186803b15801561236157600080fd5b505afa158015612375573d6000803e3d6000fd5b505050506040513d602081101561238b57600080fd5b505160108054919250908390811061239f57fe5b6000918252602090912001546001600160a01b03828116911614156123c45750612488565b80601083815481106123d257fe5b600091825260209091200180546001600160a01b0319166001600160a01b0392831617905581161561248657806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561243757600080fd5b505afa15801561244b573d6000803e3d6000fd5b505050506040513d602081101561246157600080fd5b50516001600160a01b038216600090815260066020526040902060ff909116600a0a90555b505b6001016122ff565b565b6000600160801b82106124d65760405162461bcd60e51b81526004018080602001828103825260278152602001806151656027913960400191505060405180910390fd5b5090565b60006124e582614012565b4210156040518060400160405280602081526020017f45706f6368206e6f7420726561647920666f722066696e616c697a6174696f6e8152509061256a5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50612574826132fd565b90506000601082015460ff16600581111561258b57fe5b146040518060400160405280601781526020017f45706f636820616c72656164792066696e616c697a65640000000000000000008152509061260e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50919050565b600e820154156128255761267782600e0180548060200260200160405190810160405280929190818152602001828054801561130557602002820191906000526020600020908154815260200190600101908083116112f1575050505050612d74565b600f8301558061268857600261268b565b60045b60108301805460ff191660018360058111156126a357fe5b0217905550600f82015460028054426001600160801b03908116600160801b029381166001600160801b0319909216919091171691909117905560108201546004805460ff9092169160ff191660018360058111156126fe57fe5b021790555060048054610100600160f81b031916610100426001600160f01b03160217808255601084015460ff1691906001600160f81b0316600160f81b83600581111561274857fe5b0217905550600f82015460038054426001600160801b03908116600160801b029381166001600160801b0319909216919091171691909117905561278b83610b40565b827ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3083600f015460008060008760100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156127f457fe5b8152602001828152602001965050505050505060405180910390a26001820180546001600160f81b03169055612830565b6128308383836137bc565b505050565b6060806060600084600c01549050806001600160401b038111801561285957600080fd5b50604051908082528060200260200182016040528015612883578160200160208202803683370190505b509350806001600160401b038111801561289c57600080fd5b506040519080825280602002602001820160405280156128c6578160200160208202803683370190505b5091506000816001600160401b03811180156128e157600080fd5b5060405190808252806020026020018201604052801561290b578160200160208202803683370190505b50905060005b828110156129b3576000818152600d880160205260409020805487516001600160801b039091169088908490811061294557fe5b602090810291909101015280548551600160801b9091046001600160401b03169086908490811061297257fe5b602090810291909101015280548351600160c01b9091046001600160401b03169084908490811061299f57fe5b602090810291909101015250600101612911565b506129bf868483614045565b935050509193909250565b60606129d4615002565b8351806001600160401b03811180156129ec57600080fd5b50604051908082528060200260200182016040528015612a16578160200160208202803683370190505b50925060005b81811015612a445780848281518110612a3157fe5b6020908102919091010152600101612a1c565b50612a5b6002600060018403600080888b8b61415c565b608085015260608401528083528351859185918110612a7657fe5b602002602001015181518110612a8857fe5b602090810291909101015160a0830181905260808301516060840151845192010190612ac1578251600060c08501526020840152612b15565b60048104836060015111612ae5578251606084015160c08501526020840152612b15565b612b0960016000600186600001510360008760a00151886080015101898c8c61415c565b5060c085015260208401525b825160001983011415612b3657825160006101008501526040840152612b8e565b60048104836080015111612b5b57825160808401516101008501526040840152612b8e565b612b8160038460000151600101600185038660a001518760600151016000898c8c61415c565b6101008601525060408401525b8584846000015181518110612b9f57fe5b602002602001015181518110612bb157fe5b6020908102919091010151610120840152600281048360a00151846060015101148015612bdf575060028106155b15612c0f576002612bf9846000015160018503878a614437565b8461012001510181612c0757fe5b046101208401525b612c29836020015160006000198660c00151888b8b6144f7565b60c085015260208401526040830151610100840151612c549190600019850190600190888b8b6144f7565b610100850152604084015260208301518451879186918110612c7257fe5b602002602001015181518110612c8457fe5b6020026020010151836101400181815250508584846040015181518110612ca757fe5b602002602001015181518110612cb957fe5b602090810291909101015161016084015261010083015160c08401519091030360e08301525090939092509050565b600083612cf757506000612d6c565b6000858184600019880181612d0857fe5b068152602001908152602001600020600f0154905080841415612d2f576000915050612d6c565b83612d425764e8d4a51000915050612d6c565b600084821115612d555750838103612d5a565b508084035b612d6781612710876145cb565b925050505b949350505050565b805160009080612d8057fe5b60015b81811015612e2c576000848281518110612d9957fe5b6020026020010151905060008290505b600081118015612dce575081866001830381518110612dc457fe5b6020026020010151115b15612e0a57856001820381518110612de257fe5b6020026020010151868281518110612df657fe5b602090810291909101015260001901612da9565b81868281518110612e1757fe5b60209081029190910101525050600101612d83565b506002810460018083161415612e5957838181518110612e4857fe5b6020026020010151925050506114a6565b6002848281518110612e6757fe5b6020026020010151856001840381518110612e7e57fe5b60200260200101510181612e8e57fe5b04925050506114a6565b60007f00000000000000000000000010000000000000000000000000000000000000036001600160a01b031663cd4b6914836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015612efe57600080fd5b505afa158015612f12573d6000803e3d6000fd5b505050506040513d6020811015612f2857600080fd5b50516040805160208082019390935230818301528151808203830181526060909101909152805191012092915050565b6020840151606090819060009081905b88604001518111613033576000878281518110612f8157fe5b602002602001015190506000878281518110612f9957fe5b60200260200101511115613029576000818152600d8c0160205260409020546101408b01516001600160801b0390911690811480612fe457508a6101600151816001600160801b0316145b801561301557506000828152600d8d016020526040902060010154613013908b906001600160a01b03166146d7565b155b1561302157505061302b565b506001909201915b505b600101612f68565b50806001600160401b038111801561304a57600080fd5b50604051908082528060200260200182016040528015613074578160200160208202803683370190505b509350806001600160401b038111801561308d57600080fd5b506040519080825280602002602001820160405280156130b7578160200160208202803683370190505b5060208901519093506000905b896040015181116132005760008882815181106130dd57fe5b6020026020010151905060008882815181106130f557fe5b6020026020010151905060008111156131f5576000828152600d8e0160205260409020546101408d01516001600160801b039091169081148061314557508c6101600151816001600160801b0316145b801561317657506000838152600d8f016020526040902060010154613174908d906001600160a01b03166146d7565b155b15613183575050506131f8565b6000838152600d8f01602052604090206001015489516001600160a01b03909116908a90879081106131b157fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818886815181106131de57fe5b602090810291909101015250948501946001909301925b50505b6001016130c4565b505050955095509592505050565b60007f0000000000000000000000000000000000000000000000000000000062cf1e46821015613240575060006114a6565b7f00000000000000000000000000000000000000000000000000000000000000b47f0000000000000000000000000000000000000000000000000000000062cf1e4683038161328b57fe5b0490506114a6565b604080518082018252600f81526e4654534f206e6f742061637469766560881b6020808301918252925162461bcd60e51b81526004810193845282516024820152825192939283926044909201919080838360008315610d2c578181015183820152602001610d14565b60006005817f00000000000000000000000000000000000000000000000000000000000000c8848161332b57fe5b0681526020019081526020016000209050806002015482146040518060400160405280601881526020017f45706f63682064617461206e6f7420617661696c61626c6500000000000000008152509061260e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b82516060906000816001600160401b03811180156133e257600080fd5b5060405190808252806020026020018201604052801561340c578160200160208202803683370190505b5090506000805b838110156134f55760006001600160a01b031688828151811061343257fe5b60200260200101516001600160a01b0316141561344e576134ed565b60006134c587838151811061345f57fe5b60200260200101518b60010160008c868151811061347957fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020548a85815181106134ae57fe5b60200260200101516145cb9092919063ffffffff16565b9050808483815181106134d457fe5b60209081029190910101526134e98382614712565b9250505b600101613413565b5080156135605760005b8381101561355e5761353f61353261271088848151811061351c57fe5b602002602001015161476c90919063ffffffff16565b838584815181106134ae57fe5b83828151811061354b57fe5b60209081029190910101526001016134ff565b505b509695505050505050565b8151606090806001600160401b038111801561358657600080fd5b506040519080825280602002602001820160405280156135b0578160200160208202803683370190505b50915060005b818110156136965760006001600160a01b03168582815181106135d557fe5b60200260200101516001600160a01b0316146136735760008660010160008784815181106135ff57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002054905061365561271064e8d4a510008161363d57fe5b04670de0b6b3a764000002828785815181106134ae57fe5b84838151811061366157fe5b6020026020010181815250505061368e565b600083828151811061368157fe5b6020026020010181815250505b6001016135b6565b50509392505050565b600080805b855181101561375d5760006001600160a01b03168682815181106136c457fe5b60200260200101516001600160a01b03161461375557600061271061373d8784815181106136ee57fe5b60200260200101518a60010160008b878151811061370857fe5b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020548886815181106134ae57fe5b8161374457fe5b0490506137518382614712565b9250505b6001016136a4565b5095945050505050565b600080836005015483101561377e575060006120ed565b8360060154831061379257506113886120ed565b600584015460068501546101f4919081900390850361119402816137b257fe5b0401949350505050565b82156137d8576002546001600160801b0316600f8301556137e0565b6000600f8301555b806137ec5760036137ef565b60055b60108301805460ff1916600183600581111561380757fe5b021790555060048054610100600160f81b031916610100426001600160f01b03160217808255601084015460ff1691906001600160f81b0316600160f81b83600581111561385157fe5b021790555061385f83610b40565b827ffe8865c1fe85bbf124b9e0f16cccfeeb6f330454fd79475a31261c8fa250bc3083600f015460008060008760100160009054906101000a900460ff16426040518087815260200186151581526020018581526020018481526020018360058111156138c857fe5b8152602001828152602001965050505050505060405180910390a25060010180546001600160f81b0316905550565b60408051808201909152600e81526d0a0e4d2c6ca40e8dede40d0d2ced60931b6020820152600160801b831061396e5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50613978836147c5565b6040518060400160405280601881526020017f52657665616c20706572696f64206e6f74206163746976650000000000000000815250906139fa5760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b5060006005817f00000000000000000000000000000000000000000000000000000000000000c88681613a2957fe5b0681526020810191909152604001600020600181015490915060ff600160f81b8204811691600160f01b8104909116906001600160f01b03168180613a8e5750828015613a8e57506001600160a01b0388166000908152600f602052604090205460ff165b6040518060400160405280602081526020017f45706f6368206e6f7420696e697469616c697a656420666f722072657665616c81525090613b105760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315610d2c578181015183820152602001610d14565b50600080613b21868b898887614808565b9092509050613b356005878c85858d614868565b604080518981524260208201528082018490526060810183905290518a916001600160a01b038d16917fc1b1d37612887c207efe8cb44f4069b7f10c45edaf6e8405648a94e02b6e9ec79181900360800190a350505050505050505050565b600c820154600090815b81811015613be1576000818152600d860160205260409020600101546001600160a01b0385811691161415613bd9576001019150611b319050565b600101613b9e565b506000949350505050565b7f0000000000000000000000000000000000000000000000000000000062cf1e46600182017f00000000000000000000000000000000000000000000000000000000000000b40201919050565b6060806060613c466122b6565b6010805480602002602001604051908101604052809291908181526020018280548015613c9c57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311613c7e575b5050505050925082516001600160401b0381118015613cba57600080fd5b50604051908082528060200260200182016040528015613ce4578160200160208202803683370190505b50915082516001600160401b0381118015613cfe57600080fd5b50604051908082528060200260200182016040528015613d28578160200160208202803683370190505b50905060005b8351811015613e2957613d62848281518110613d4657fe5b60209081029190910101516007546001600160f01b0316613e2f565b838281518110613d6e57fe5b60200260200101818152505060118181548110613d8757fe5b9060005260206000200160009054906101000a90046001600160a01b03166001600160a01b031663eb91d37e6040518163ffffffff1660e01b8152600401604080518083038186803b158015613ddc57600080fd5b505afa158015613df0573d6000803e3d6000fd5b505050506040513d6040811015613e0657600080fd5b50518251839083908110613e1657fe5b6020908102919091010152600101613d2e565b50909192565b60006001600160a01b038316613e4757506000611b31565b826001600160a01b031663caeb942b836040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b158015613e8d57600080fd5b505af1158015613ea1573d6000803e3d6000fd5b505050506040513d6020811015613eb757600080fd5b50519050611b31565b613ecd87878585856149b8565b6007870154600380880191909155600888015460048801556005870186905560068701859055870154613f08908581613f0257fe5b04612492565b86546001600160801b0319166001600160801b039190911617865560048701546007870154613f39919081613f0257fe5b86546001600160801b03918216600160801b0291161786555050506001909201805460ff60f01b1916600160f01b179055505050565b60006001600160a01b038416613f87575060006120ed565b836001600160a01b031663e587497e84846040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b158015613fde57600080fd5b505af1158015613ff2573d6000803e3d6000fd5b505050506040513d602081101561400857600080fd5b5051949350505050565b60007f000000000000000000000000000000000000000000000000000000000000005a61403e83613bec565b0192915050565b600c830154606090806001600160401b038111801561406357600080fd5b5060405190808252806020026020018201604052801561408d578160200160208202803683370190505b509150600061409b85614a21565b905060006140a885614a21565b90506000806140b8898585614a66565b905083156140c857806127100391505b60005b8581101561414f57600083156141115761410e8a83815181106140ea57fe5b602002602001015164e8d4a51000026127108802866145cb9092919063ffffffff16565b90505b6000831561412b576141288a84815181106140ea57fe5b90505b80820189848151811061413a57fe5b602090810291909101015250506001016140cb565b5050505050509392505050565b6000806000888a1415614176575088915086905085614429565b61417e615081565b888152602081018890526141906150be565b60208082018d905260408083018d90528051448184015242818301528151808203830181526060909101909152805191012060005b602083015160408401516141f791908160018183030186816141e357fe5b0601876000015188602001518f8f8f614acd565b606087015260408601528084528a518b9190811061421157fe5b60200260200101518360600181815250508783606001518151811061423257fe5b6020026020010151846080018181525050836060015184604001518560800151010190508e6002141561430d5760028106600282040160a0850181905280820360c086015260408501511080159061428d57508b8460a00151115b156142b257825160001901604084015260608401516080850151016020850152614308565b8360c0015184606001511180156142cc57508a8460c00151115b156142ed578251600101602084015260408401516080850151018452614308565b50505160408201516060909201519094509092509050614429565b614424565b8e6001141561439e576004810460a0850181905280820360c0860152604085015111801561433e57508b8460a00151115b1561436357825160001901604084015260608401516080850151016020850152614308565b8360c001518460600151101580156142cc57508a8460c0015111156142ed578251600101602084015260408401516080850151018452614308565b6004810460c08501819052810360a085018190526040850151108015906143c857508b8460a00151115b156143ed57825160001901604084015260608401516080850151016020850152614424565b8360c00151846060015111801561440757508a8460c00151115b156142ed5782516001016020840152604084015160808501510184525b6141c5565b985098509895505050505050565b60008385141561446f578183868151811061444e57fe5b60200260200101518151811061446057fe5b60200260200101519050612d6c565b60008284876001018151811061448157fe5b60200260200101518151811061449357fe5b602002602001015190506000808760020190505b8681116144eb57848682815181106144bb57fe5b6020026020010151815181106144cd57fe5b60200260200101519150828210156144e3578192505b6001016144a7565b50909695505050505050565b60008085888a038802821361451257898792509250506145bf565b600085878c8151811061452157fe5b60200260200101518151811061453357fe5b602090810291909101015190508a89016000815b60008c8e830302136145b35789818151811061455f57fe5b602002602001015191508389838151811061457657fe5b602002602001015114156145ac5787828151811061459057fe5b6020026020010151850394506145a783828c614c96565b918b01915b8b01614547565b50508990039350909150505b97509795505050505050565b6000808211614614576040805162461bcd60e51b815260206004820152601060248201526f4469766973696f6e206279207a65726f60801b604482015290519081900360640190fd5b83614621575060006120ed565b8383028385828161462e57fe5b0414156146475782818161463e57fe5b049150506120ed565b600083868161465257fe5b049050600084878161466057fe5b069050600085878161466e57fe5b049050600086888161467c57fe5b0690506146ca61469688614690868561476c565b90614cfe565b6146c46146a3868661476c565b6146c46146b0898761476c565b6146c48d6146be8c8b61476c565b9061476c565b90614712565b9998505050505050505050565b604080516020808201949094526001600160a01b03929092168282015280518083038201815260609092019052805191012060019081161490565b6000828201838110156120ed576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60008261477b57506000611b31565b8282028284828161478857fe5b04146120ed5760405162461bcd60e51b815260040180806020018281038252602181526020018061518c6021913960400191505060405180910390fd5b6000806147d183613bec565b90504281111580156120ed57507f000000000000000000000000000000000000000000000000000000000000005a01421092915050565b600080831561481c5750600090508061485e565b84915061482a878785614d65565b87549091506001600160801b0380821691600160801b9004168184111561484f578193505b8083111561485b578092505b50505b9550959350505050565b600085600c0154905060006148898686868a600601548b6007015488614e23565b63ffffffff838116608083019081526000858152600d8b0160209081526040918290208551815492870151938701516001600160801b03199093166001600160801b039091161767ffffffffffffffff60801b1916600160801b6001600160401b0394851602176001600160c01b0316600160c01b939092169290920217815560608401516001918201805493516001600160a01b03199094166001600160a01b039092169190911763ffffffff60a01b1916600160a01b93851693909302929092179091558401600c8a0155600889015491925061496b9190879061471216565b60088801556001600160a01b0386166000908152600a8901602052604090205460ff16156149ae57600e8701805460018101825560009182526020909120018390555b5050505050505050565b82516149cd90600a8601906020860190614fa1565b506149da858484846133c5565b80516149f091600b8701916020909101906150e6565b5060006149fe868685614e80565b600786018190559050614a118682613767565b8560090181905550505050505050565b600080805b8351811015614a5f57614a55848281518110614a3e57fe5b60200260200101518361471290919063ffffffff16565b9150600101614a26565b5092915050565b600081614a75575060006120ed565b82614a8357506127106120ed565b6000614a978361271064e8d4a510006145cb565b905084600301548110614ab057505060098301546120ed565b60038501546009860154614ac59183906145cb565b9150506120ed565b60008060008085878b81518110614ae057fe5b602002602001015181518110614af257fe5b60200260200101519050600060026001600160401b0381118015614b1557600080fd5b50604051908082528060200260200182016040528015614b3f578160200160208202803683370190505b5090508981600081518110614b5057fe5b6020026020010181815250508881600181518110614b6a57fe5b60209081029190910101528c8c614b828d828c614c96565b81805b82811015614c465760008c8281518110614b9b57fe5b60200260200101519050868c8281518110614bb257fe5b60200260200101511015614c09578a8181518110614bcc57fe5b602002602001015186600081518110614be157fe5b602002602001018181510191508181525050614bfe83838f614c96565b600190920191614c3d565b8a8181518110614c1557fe5b602002602001015186600181518110614c2a57fe5b6020026020010181815101915081815250505b50600101614b85565b50614c5282828d614c96565b8084600081518110614c6057fe5b602002602001015185600181518110614c7557fe5b60200260200101519750975097505050505050985098509895505050505050565b81831415614ca357612830565b808281518110614caf57fe5b6020026020010151818481518110614cc357fe5b6020026020010151828581518110614cd757fe5b60200260200101838581518110614cea57fe5b602090810291909101019190915252505050565b6000808211614d54576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381614d5d57fe5b049392505050565b600a83015460009081906001600160401b0381118015614d8457600080fd5b50604051908082528060200260200182016040528015614dae578160200160208202803683370190505b50905060005b600a860154811015614e0d57614dee86600a018281548110614dd257fe5b6000918252602090912001546001600160a01b03168686613f6f565b828281518110614dfa57fe5b6020908102919091010152600101614db4565b50614e1a60058683614e80565b95945050505050565b614e2b615121565b6001600160a01b0387166060820152614e448685614f70565b6001600160401b03166020820152614e5c8584614f70565b6001600160401b031660408201526001600160801b03909116815295945050505050565b600080805b600a850154811015614f675760006001600160a01b031685600a018281548110614eab57fe5b6000918252602090912001546001600160a01b03161415614ecb57614f5f565b614f5c612710614f4c868481518110614ee057fe5b60200260200101518960010160008a600a018781548110614efd57fe5b60009182526020808320909101546001600160a01b03168352820192909252604001902054600b8a01805487908110614f3257fe5b90600052602060002001546145cb9092919063ffffffff16565b81614f5357fe5b84919004614712565b91505b600101614e85565b50949350505050565b6000811580614f7d575082155b15614f8a57506000611b31565b614f9a8364e8d4a51000846145cb565b9050611b31565b828054828255906000526020600020908101928215614ff6579160200282015b82811115614ff657825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190614fc1565b506124d692915061514f565b6040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b5080546000825590600052602060002090810190610b40919061514f565b6040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215614ff6579160200282015b82811115614ff6578251825591602001919060010190615106565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b5b808211156124d6576000815560010161515056fe53616665436173743a2076616c756520646f65736e27742066697420696e203132382062697473536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220a0feb28364f05773bf3bdc810cf6412e41f6fe7ef2c4a4adf535c3c83494e52564736f6c63430007060033