// Contract: MetadataOracle
// Target function: function updateDeltas(bytes calldata deltas, uint64 pricesHashCheck) external onlyOwner
 >> 128;
      if (x & 0x40000 > 0)
        result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
      if (x & 0x20000 > 0)
        result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;
      if (x & 0x10000 > 0)
        result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
      if (x & 0x8000 > 0)
        result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
      if (x & 0x4000 > 0)
        result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;
      if (x & 0x2000 > 0)
        result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;
      if (x & 0x1000 > 0)
        result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
      if (x & 0x800 > 0)
        result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
      if (x & 0x400 > 0)
        result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;
      if (x & 0x200 > 0)
        result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;
      if (x & 0x100 > 0)
        result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;
      if (x & 0x80 > 0)
        result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
      if (x & 0x40 > 0)
        result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
      if (x & 0x20 > 0)
        result = result * 0x100000000000000162E42FEFA39EF366F >> 128;
      if (x & 0x10 > 0)
        result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;
      if (x & 0x8 > 0)
        result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
      if (x & 0x4 > 0)
        result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
      if (x & 0x2 > 0)
        result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;
      if (x & 0x1 > 0)
        result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;

      result >>= uint256 (int256 (63 - (x >> 64)));
      require (result <= uint256 (int256 (MAX_64x64)));

      return int128 (int256 (result));
    }
  }

  /**
   * Calculate natural exponent of x.  Revert on overflow.
   *
   * @param x signed 64.64-bit fixed point number
   * @return signed 64.64-bit fixed point number
   */
  function exp (int128 x) internal pure returns (int128) {
    unchecked {
      require (x < 0x400000000000000000); // Overflow

      if (x < -0x400000000000000000) return 0; // Underflow

      return exp_2 (
          int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));
    }
  }

  /**
   * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
   * integer numbers.  Revert on overflow or when y is zero.
   *
   * @param x unsigned 256-bit integer number
   * @param y unsigned 256-bit integer number
   * @return unsigned 64.64-bit fixed point number
   */
  function divuu (uint256 x, uint256 y) private pure returns (uint128) {
    unchecked {
      require (y != 0);

      uint256 result;

      if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        result = (x << 64) / y;
      else {
        uint256 msb = 192;
        uint256 xc = x >> 192;
        if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
        if (xc >= 0x10000) { xc >>= 16; msb += 16; }
        if (xc >= 0x100) { xc >>= 8; msb += 8; }
        if (xc >= 0x10) { xc >>= 4; msb += 4; }
        if (xc >= 0x4) { xc >>= 2; msb += 2; }
        if (xc >= 0x2) msb += 1;  // No need to shift xc anymore

        result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);
        require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

        uint256 hi = result * (y >> 128);
        uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

        uint256 xh = x >> 192;
        uint256 xl = x << 64;

        if (xl < lo) xh -= 1;
        xl -= lo; // We rely on overflow behavior here
        lo = hi << 128;
        if (xl < lo) xh -= 1;
        xl -= lo; // We rely on overflow behavior here

        assert (xh == hi >> 128);

        result += xl / y;
      }

      require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
      return uint128 (result);
    }
  }

  /**
   * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer
   * number.
   *
   * @param x unsigned 256-bit integer number
   * @return unsigned 128-bit integer number
   */
  function sqrtu (uint256 x) private pure returns (uint128) {
    unchecked {
      if (x == 0) return 0;
      else {
        uint256 xx = x;
        uint256 r = 1;
        if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }
        if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }
        if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }
        if (xx >= 0x10000) { xx >>= 16; r <<= 8; }
        if (xx >= 0x100) { xx >>= 8; r <<= 4; }
        if (xx >= 0x10) { xx >>= 4; r <<= 2; }
        if (xx >= 0x8) { r <<= 1; }
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1; // Seven iterations should be enough
        uint256 r1 = x / r;
        return uint128 (r < r1 ? r : r1);
      }
    }
  }
}


/// @title Reader of MetadataOracle core data
interface ICoreMetadataOracleReader {

    struct Quote {
        uint256 price;
        uint32 updateTS;
    }


    /// @notice Gets a list of assets quoted by this oracle.
    function getAssets() external view returns (address[] memory);

    /// @notice Checks if an asset is quoted by this oracle.
    function hasAsset(address asset) external view returns (bool);

    /**
     * @notice Gets last known quotes for the assets.
     * @param assets Assets to query
     * @return quotes Prices and update timestamps for corresponding assets.
     */
    function quoteAssets(address[] calldata assets) external view returns (Quote[] memory quotes);
}


/**
 * @notice Write-efficient oracle
 */
contract MetadataOracle is ICoreMetadataOracleReader, Ownable {
    using ABDKMath64x64 for int128;

    // Signed 64.64 fixed point number 1.002 gives us minimal distinguishable price change of 0.2%.
    int128 public constant DELTA_BASE = int128(uint128(1002 * 2 ** 64) / uint128(1000));

    // min delta is DELTA_BASE ** -512, max delta is DELTA_BASE ** 511
    uint256 public constant DELTA_BITS = 10;

    // keccak256("MetadataOracle.deltas")
    uint256 private constant DELTAS_LOCATION = 0x13564db6094c800c2f97ce40db2ed0766673b9ca1c1adc61480074f2544395d8;

    uint256 private constant NO_DELTA = 0;
    uint256 private constant DELTA_MODULO = 1 << DELTA_BITS;        // module for two's complement arithmetic
    uint256 private constant DELTA_MASK = DELTA_MODULO - 1;         // mask to extract a delta
    uint256 private constant DELTAS_PER_SLOT = 256 / DELTA_BITS;    // note that there may be unused bits in a slot
    uint256 private constant SLOT_PADDING_BITS = 256 - DELTAS_PER_SLOT * DELTA_BITS;    // unused bits in a slot

    int128 private constant ABDK_ONE = int128(int256(1 << 64));


    struct Status {
        // TODO replace with per-asset update timestamps
        // and
        // externally set epoch ts for replay attack protection: current epoch < new epoch <= now.
        uint32 updateTS;    // last unix timestamp of ANY update

        // pricesHash was used instead of nonces since the same nonce may be associated with different
        // base prices arrays in different chain forks. That is unacceptable for the delta application.
        // So we had to check the entire prices content for equality.
        // TODO replace with current epoch as a collision preventer.
        // Now this is acceptable since prices for an epoch are deterministic.
        uint64 pricesHash;  // for updater only!
    }

    Status internal status;


    address[] internal assets;
    mapping(address => uint) internal assetId;

    uint[] internal prices;


    /// @inheritdoc ICoreMetadataOracleReader
    function getAssets() external view override returns (address[] memory) {
        return assets;
    }

    function getStatus() external view returns (Status memory) {
        return status;
    }

    // prices WITHOUT deltas applied! for updater only!
    function getBasePrices() external view returns (uint[] memory) {
        return prices;
    }

    // for updater only!
    function getCurrentDeltas() external view returns (uint[] memory result) {
        result = new uint[](assets.length);
        // TODO optimize excess sload-s
        for (uint i = 0; i < result.length; i++)
            result[i] = getDelta(i);
    }

    /// @inheritdoc ICoreMetadataOracleReader
    function hasAsset(address asset) public view override returns (bool) {
        return assetId[asset] != 0 || (assets.length != 0 && assets[0] == asset);
    }


    /// @inheritdoc ICoreMetadataOracleReader
    function quoteAssets(address[] calldata assets_) external view override returns (Quote[] memory quotes) {
        uint32 updateTS = status.updateTS;
        uint256 length = assets_.length;
        quotes = new Quote[](length);
        for (uint i = 0; i < length; i++) {
            address asset = assets_[i];
            uint256 id = assetId[asset];

            require(id != 0 || (assets.length != 0 && assets[0] == asset), "MetadataOracle: ASSET_NOT_FOUND");

            // TODO optimize excess sload-s
            quotes[i] = Quote(getPrice(id), updateTS);
        }
    }


    function addAsset(address asset, uint256 currentPrice) external onlyOwner {
        require(!hasAsset(asset), "MetadataOracle: ASSET_EXISTS");

        assetId[asset] = assets.length;
        assets.push(asset);
        prices.push(currentPrice);
        setDelta(assetId[asset], NO_DELTA);

        updated();
        addToPricesHash(asset, currentPrice);
    }

    function addAssets(address[] calldata assets_, uint256[] calldata prices_) external onlyOwner {
        require(assets_.length != 0 && assets_.length == prices_.length, "MetadataOracle: BAD_LENGTH");

        uint256 length = assets_.length;
        uint64 pricesHash = status.pricesHash;
        uint256 id = assets.length;
        for (uint256 i = 0; i < length; i++) {
            address asset = assets_[i];
            uint256 currentPrice = prices_[i];
            require(!hasAsset(asset), "MetadataOracle: ASSET_EXISTS");

            assetId[asset] = id;
            assets.push(asset);
            prices.push(currentPrice);
            setDelta(id, NO_DELTA);
            id++;

            pricesHash = uint64(uint256(keccak256(abi.encodePacked(pricesHash, asset, currentPrice))));
        }

        status.pricesHash = pricesHash;
        updated();
    }

    function setPrice(address asset, uint256 price) external onlyOwner {
        require(hasAsset(asset), "MetadataOracle: ASSET_NOT_FOUND");

        uint256 id = assetId[asset];
        prices[id] = price;
        setDelta(id, NO_DELTA);

        updated();
        addToPricesHash(asset, price);
    }

    function setPrices(address[] calldata assets_, uint256[] calldata prices_) external onlyOwner {
        require(assets_.length != 0 && assets_.length == prices_.length, "MetadataOracle: BAD_LENGTH");

        uint256 length = assets_.length;
        uint64 pricesHash = status.pricesHash;
        for (uint256 i = 0; i < length; i++) {
            address asset = assets_[i];
            uint256 price = prices_[i];
            require(hasAsset(asset), "MetadataOracle: ASSET_NOT_FOUND");

            uint256 id = assetId[asset];
            prices[id] = price;
            setDelta(id, NO_DELTA);

            pricesHash = uint64(uint256(keccak256(abi.encodePacked(pricesHash, asset, price))));
        }

        status.pricesHash = pricesHash;
        updated();
    }