S Price: $0.447935 (+4.89%)

Contract Diff Checker

Contract Name:
SonicMultiFeedAdapterWithoutRoundsV1

Contract Source Code:

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import "./RedstoneConstants.sol";

/**
 * @title The base contract with the main logic of data extraction from calldata
 * @author The Redstone Oracles team
 * @dev This contract was created to reuse the same logic in the RedstoneConsumerBase
 * and the ProxyConnector contracts
 */
contract CalldataExtractor is RedstoneConstants {

  error DataPackageTimestampMustNotBeZero();
  error DataPackageTimestampsMustBeEqual();
  error RedstonePayloadMustHaveAtLeastOneDataPackage();
  error TooLargeValueByteSize(uint256 valueByteSize);

  function extractTimestampsAndAssertAllAreEqual() public pure returns (uint256 extractedTimestamp) {
    uint256 calldataNegativeOffset = _extractByteSizeOfUnsignedMetadata();
    uint256 dataPackagesCount;
    (dataPackagesCount, calldataNegativeOffset) = _extractDataPackagesCountFromCalldata(calldataNegativeOffset);

    if (dataPackagesCount == 0) {
      revert RedstonePayloadMustHaveAtLeastOneDataPackage();
    }

    for (uint256 dataPackageIndex = 0; dataPackageIndex < dataPackagesCount; dataPackageIndex++) {
      uint256 dataPackageByteSize = _getDataPackageByteSize(calldataNegativeOffset);

      // Extracting timestamp for the current data package
      uint48 dataPackageTimestamp; // uint48, because timestamp uses 6 bytes
      uint256 timestampNegativeOffset = (calldataNegativeOffset + TIMESTAMP_NEGATIVE_OFFSET_IN_DATA_PACKAGE_WITH_STANDARD_SLOT_BS);
      uint256 timestampOffset = msg.data.length - timestampNegativeOffset;
      assembly {
        dataPackageTimestamp := calldataload(timestampOffset)
      }

      if (dataPackageTimestamp == 0) {
        revert DataPackageTimestampMustNotBeZero();
      }

      if (extractedTimestamp == 0) {
        extractedTimestamp = dataPackageTimestamp;
      } else if (dataPackageTimestamp != extractedTimestamp) {
        revert DataPackageTimestampsMustBeEqual();
      }

      calldataNegativeOffset += dataPackageByteSize;
    }
  }

  function _getDataPackageByteSize(uint256 calldataNegativeOffset) internal pure returns (uint256) {
    (
      uint256 dataPointsCount,
      uint256 eachDataPointValueByteSize
    ) = _extractDataPointsDetailsForDataPackage(calldataNegativeOffset);

    return
      dataPointsCount *
      (DATA_POINT_SYMBOL_BS + eachDataPointValueByteSize) +
      DATA_PACKAGE_WITHOUT_DATA_POINTS_BS;
  }

  function _extractByteSizeOfUnsignedMetadata() internal pure returns (uint256) {
    // Checking if the calldata ends with the RedStone marker
    bool hasValidRedstoneMarker;
    assembly {
      let calldataLast32Bytes := calldataload(sub(calldatasize(), STANDARD_SLOT_BS))
      hasValidRedstoneMarker := eq(
        REDSTONE_MARKER_MASK,
        and(calldataLast32Bytes, REDSTONE_MARKER_MASK)
      )
    }
    if (!hasValidRedstoneMarker) {
      revert CalldataMustHaveValidPayload();
    }

    // Using uint24, because unsigned metadata byte size number has 3 bytes
    uint24 unsignedMetadataByteSize;
    if (REDSTONE_MARKER_BS_PLUS_STANDARD_SLOT_BS > msg.data.length) {
      revert CalldataOverOrUnderFlow();
    }
    assembly {
      unsignedMetadataByteSize := calldataload(
        sub(calldatasize(), REDSTONE_MARKER_BS_PLUS_STANDARD_SLOT_BS)
      )
    }
    uint256 calldataNegativeOffset = unsignedMetadataByteSize
      + UNSIGNED_METADATA_BYTE_SIZE_BS
      + REDSTONE_MARKER_BS;
    if (calldataNegativeOffset + DATA_PACKAGES_COUNT_BS > msg.data.length) {
      revert IncorrectUnsignedMetadataSize();
    }
    return calldataNegativeOffset;
  }

  // We return uint16, because unsigned metadata byte size number has 2 bytes
  function _extractDataPackagesCountFromCalldata(uint256 calldataNegativeOffset)
    internal
    pure
    returns (uint16 dataPackagesCount, uint256 nextCalldataNegativeOffset)
  {
    uint256 calldataNegativeOffsetWithStandardSlot = calldataNegativeOffset + STANDARD_SLOT_BS;
    if (calldataNegativeOffsetWithStandardSlot > msg.data.length) {
      revert CalldataOverOrUnderFlow();
    }
    assembly {
      dataPackagesCount := calldataload(
        sub(calldatasize(), calldataNegativeOffsetWithStandardSlot)
      )
    }
    return (dataPackagesCount, calldataNegativeOffset + DATA_PACKAGES_COUNT_BS);
  }

  function _extractDataPointValueAndDataFeedId(
    uint256 dataPointNegativeOffset,
    uint256 dataPointValueByteSize
  ) internal pure virtual returns (bytes32 dataPointDataFeedId, uint256 dataPointValue) {
    uint256 dataPointCalldataOffset = msg.data.length - dataPointNegativeOffset;
    assembly {
      dataPointDataFeedId := calldataload(dataPointCalldataOffset)
      dataPointValue := calldataload(add(dataPointCalldataOffset, DATA_POINT_SYMBOL_BS))
    }
    if (dataPointValueByteSize >= 33) {
      revert TooLargeValueByteSize(dataPointValueByteSize);
    }
    unchecked {
      dataPointValue = dataPointValue >> (32 - dataPointValueByteSize) * 8; 
    }
  }

  function _extractDataPointsDetailsForDataPackage(uint256 calldataNegativeOffsetForDataPackage)
    internal
    pure
    returns (uint256 dataPointsCount, uint256 eachDataPointValueByteSize)
  {
    // Using uint24, because data points count byte size number has 3 bytes
    uint24 dataPointsCount_;

    // Using uint32, because data point value byte size has 4 bytes
    uint32 eachDataPointValueByteSize_;

    // Extract data points count
    uint256 calldataOffset = msg.data.length - (calldataNegativeOffsetForDataPackage + SIG_BS + STANDARD_SLOT_BS);
    assembly {
      dataPointsCount_ := calldataload(calldataOffset)
    }

    // Extract each data point value size
    calldataOffset = calldataOffset - DATA_POINTS_COUNT_BS;
    assembly {
      eachDataPointValueByteSize_ := calldataload(calldataOffset)
    }

    // Prepare returned values
    dataPointsCount = dataPointsCount_;
    eachDataPointValueByteSize = eachDataPointValueByteSize_;
  }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

/**
 * @title The base contract with helpful constants
 * @author The Redstone Oracles team
 * @dev It mainly contains redstone-related values, which improve readability
 * of other contracts (e.g. CalldataExtractor and RedstoneConsumerBase)
 */
contract RedstoneConstants {
  // === Abbreviations ===
  // BS - Bytes size
  // PTR - Pointer (memory location)
  // SIG - Signature

  // Solidity and YUL constants
  uint256 internal constant STANDARD_SLOT_BS = 32;
  uint256 internal constant FREE_MEMORY_PTR = 0x40;
  uint256 internal constant BYTES_ARR_LEN_VAR_BS = 32;
  uint256 internal constant REVERT_MSG_OFFSET = 68; // Revert message structure described here: https://ethereum.stackexchange.com/a/66173/106364
  uint256 internal constant STRING_ERR_MESSAGE_MASK = 0x08c379a000000000000000000000000000000000000000000000000000000000;

  // RedStone protocol consts
  uint256 internal constant SIG_BS = 65;
  uint256 internal constant TIMESTAMP_BS = 6;
  uint256 internal constant DATA_PACKAGES_COUNT_BS = 2;
  uint256 internal constant DATA_POINTS_COUNT_BS = 3;
  uint256 internal constant DATA_POINT_VALUE_BYTE_SIZE_BS = 4;
  uint256 internal constant DATA_POINT_SYMBOL_BS = 32;
  uint256 internal constant DEFAULT_DATA_POINT_VALUE_BS = 32;
  uint256 internal constant UNSIGNED_METADATA_BYTE_SIZE_BS = 3;
  uint256 internal constant REDSTONE_MARKER_BS = 9; // byte size of 0x000002ed57011e0000
  uint256 internal constant REDSTONE_MARKER_MASK = 0x0000000000000000000000000000000000000000000000000002ed57011e0000;

  // Derived values (based on consts)
  uint256 internal constant TIMESTAMP_NEGATIVE_OFFSET_IN_DATA_PACKAGE_WITH_STANDARD_SLOT_BS = 104; // SIG_BS + DATA_POINTS_COUNT_BS + DATA_POINT_VALUE_BYTE_SIZE_BS + STANDARD_SLOT_BS
  uint256 internal constant DATA_PACKAGE_WITHOUT_DATA_POINTS_BS = 78; // DATA_POINT_VALUE_BYTE_SIZE_BS + TIMESTAMP_BS + DATA_POINTS_COUNT_BS + SIG_BS
  uint256 internal constant DATA_PACKAGE_WITHOUT_DATA_POINTS_AND_SIG_BS = 13; // DATA_POINT_VALUE_BYTE_SIZE_BS + TIMESTAMP_BS + DATA_POINTS_COUNT_BS
  uint256 internal constant REDSTONE_MARKER_BS_PLUS_STANDARD_SLOT_BS = 41; // REDSTONE_MARKER_BS + STANDARD_SLOT_BS

  // Error messages
  error CalldataOverOrUnderFlow();
  error IncorrectUnsignedMetadataSize();
  error InsufficientNumberOfUniqueSigners(uint256 receivedSignersCount, uint256 requiredSignersCount);
  error EachSignerMustProvideTheSameValue();
  error EmptyCalldataPointersArr();
  error InvalidCalldataPointer();
  error CalldataMustHaveValidPayload();
  error SignerNotAuthorised(address receivedSigner);
  error DataTimestampCannotBeZero();
  error TimestampsMustBeEqual();
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import "./RedstoneConstants.sol";
import "./RedstoneDefaultsLib.sol";
import "./CalldataExtractor.sol";
import "../libs/BitmapLib.sol";
import "../libs/SignatureLib.sol";

/**
 * @title The base contract with the main Redstone logic
 * @author The Redstone Oracles team
 * @dev Do not use this contract directly in consumer contracts, take a
 * look at `RedstoneConsumerNumericBase` and `RedstoneConsumerBytesBase` instead
 */
abstract contract RedstoneConsumerBase is CalldataExtractor {

  error GetDataServiceIdNotImplemented();

  /* ========== VIRTUAL FUNCTIONS (MAY BE OVERRIDDEN IN CHILD CONTRACTS) ========== */

  /**
   * @dev This function must be implemented by the child consumer contract.
   * It should return dataServiceId which DataServiceWrapper will use if not provided explicitly .
   * If not overridden, value will always have to be provided explicitly in DataServiceWrapper.
   * @return dataServiceId being consumed by contract
   */
  function getDataServiceId() public view virtual returns (string memory) {
    revert GetDataServiceIdNotImplemented();
  }

  /**
   * @dev This function must be implemented by the child consumer contract.
   * It should return a unique index for a given signer address if the signer
   * is authorised, otherwise it should revert
   * @param receivedSigner The address of a signer, recovered from ECDSA signature
   * @return Unique index for a signer in the range [0..255]
   */
  function getAuthorisedSignerIndex(address receivedSigner) public view virtual returns (uint8);

  /**
   * @dev This function may be overridden by the child consumer contract.
   * It should validate the timestamp against the current time (block.timestamp)
   * It should revert with a helpful message if the timestamp is not valid
   * @param receivedTimestampMilliseconds Timestamp extracted from calldata
   */
  function validateTimestamp(uint256 receivedTimestampMilliseconds) public view virtual {
    RedstoneDefaultsLib.validateTimestamp(receivedTimestampMilliseconds);
  }

  /**
   * @dev This function must be implemented by the child consumer contract.
   * @return The minimum required value of unique authorised signers
   */
  function getUniqueSignersThreshold() public view virtual returns (uint8);

  /**
   * @dev This function may be overridden by the child consumer contract.
   * It should aggregate values from different signers to a single uint value.
   * By default, it calculates the median value
   * @param values An array of uint256 values from different signers
   * @return Result of the aggregation in the form of a single number
   */
  function aggregateValues(uint256[] memory values) public view virtual returns (uint256) {
    return RedstoneDefaultsLib.aggregateValues(values);
  }

  /* ========== FUNCTIONS WITH IMPLEMENTATION (CAN NOT BE OVERRIDDEN) ========== */

  /**
   * @dev This is an internal helpful function for secure extraction oracle values
   * from the tx calldata. Security is achieved by signatures verification, timestamp
   * validation, and aggregating values from different authorised signers into a
   * single numeric value. If any of the required conditions (e.g. packages with different 
   * timestamps or insufficient number of authorised signers) do not match, the function 
   * will revert.
   *
   * Note! You should not call this function in a consumer contract. You can use
   * `getOracleNumericValuesFromTxMsg` or `getOracleNumericValueFromTxMsg` instead.
   *
   * @param dataFeedIds An array of unique data feed identifiers
   * @return An array of the extracted and verified oracle values in the same order
   * as they are requested in dataFeedIds array
   * @return dataPackagesTimestamp timestamp equal for all data packages
   */
  function _securelyExtractOracleValuesAndTimestampFromTxMsg(bytes32[] memory dataFeedIds)
    internal
    view
    returns (uint256[] memory, uint256 dataPackagesTimestamp)
  {
    // Initializing helpful variables and allocating memory
    uint256[] memory uniqueSignerCountForDataFeedIds = new uint256[](dataFeedIds.length);
    uint256[] memory signersBitmapForDataFeedIds = new uint256[](dataFeedIds.length);
    uint256[][] memory valuesForDataFeeds = new uint256[][](dataFeedIds.length);
    for (uint256 i = 0; i < dataFeedIds.length;) {
      // The line below is commented because newly allocated arrays are filled with zeros
      // But we left it for better readability
      // signersBitmapForDataFeedIds[i] = 0; // <- setting to an empty bitmap
      valuesForDataFeeds[i] = new uint256[](getUniqueSignersThreshold());
      unchecked {
        i++;
      }
    }

    // Extracting the number of data packages from calldata
    uint256 calldataNegativeOffset = _extractByteSizeOfUnsignedMetadata();
    uint256 dataPackagesCount;
    (dataPackagesCount, calldataNegativeOffset) = _extractDataPackagesCountFromCalldata(calldataNegativeOffset);

    // Saving current free memory pointer
    uint256 freeMemPtr;
    assembly {
      freeMemPtr := mload(FREE_MEMORY_PTR)
    }

    // Data packages extraction in a loop
    for (uint256 dataPackageIndex = 0; dataPackageIndex < dataPackagesCount;) {
      // Extract data package details and update calldata offset
      uint256 dataPackageTimestamp;
      (calldataNegativeOffset, dataPackageTimestamp) = _extractDataPackage(
        dataFeedIds,
        uniqueSignerCountForDataFeedIds,
        signersBitmapForDataFeedIds,
        valuesForDataFeeds,
        calldataNegativeOffset
      );

      if (dataPackageTimestamp == 0) {
        revert DataTimestampCannotBeZero();
      }

      if (dataPackageTimestamp != dataPackagesTimestamp) {
        if (dataPackagesTimestamp == 0) {
          // Setting dataPackagesTimestamp first time
          dataPackagesTimestamp = dataPackageTimestamp;    
        } else {
          revert TimestampsMustBeEqual();
        }
      }

      // Resetting the memory pointer to the initial "safe" value
      // We add STANDARD_SLOT_BS (32 bytes) to account for potential allocation
      // of the dataPackageIndex variable, which may or may not be stored in memory
      assembly {
        mstore(FREE_MEMORY_PTR, add(freeMemPtr, STANDARD_SLOT_BS))
      }
      unchecked {
        dataPackageIndex++;
      }
    }

    // Validating numbers of unique signers and calculating aggregated values for each dataFeedId
    return (_getAggregatedValues(valuesForDataFeeds, uniqueSignerCountForDataFeedIds), dataPackagesTimestamp);
  }

  /**
   * @dev This is a private helpful function, which extracts data for a data package based
   * on the given negative calldata offset, verifies them, and in the case of successful
   * verification updates the corresponding data package values in memory
   *
   * @param dataFeedIds an array of unique data feed identifiers
   * @param uniqueSignerCountForDataFeedIds an array with the numbers of unique signers
   * for each data feed
   * @param signersBitmapForDataFeedIds an array of signer bitmaps for data feeds
   * @param valuesForDataFeeds 2-dimensional array, valuesForDataFeeds[i][j] contains
   * j-th value for the i-th data feed
   * @param calldataNegativeOffset negative calldata offset for the given data package
   *
   * @return nextCalldataNegativeOffset negative calldata offset for the next data package
   * @return dataPackageTimestamp data package timestamp
   */
  function _extractDataPackage(
    bytes32[] memory dataFeedIds,
    uint256[] memory uniqueSignerCountForDataFeedIds,
    uint256[] memory signersBitmapForDataFeedIds,
    uint256[][] memory valuesForDataFeeds,
    uint256 calldataNegativeOffset
  ) private view returns (uint256 nextCalldataNegativeOffset, uint256 dataPackageTimestamp) {
    uint256 signerIndex;

    (
      uint256 dataPointsCount,
      uint256 eachDataPointValueByteSize
    ) = _extractDataPointsDetailsForDataPackage(calldataNegativeOffset);

    // We use scopes to resolve problem with too deep stack
    {
      address signerAddress;
      bytes32 signedHash;
      bytes memory signedMessage;
      uint256 signedMessageBytesCount;
      uint48 extractedTimestamp;

      signedMessageBytesCount = dataPointsCount * (eachDataPointValueByteSize + DATA_POINT_SYMBOL_BS)
        + DATA_PACKAGE_WITHOUT_DATA_POINTS_AND_SIG_BS; //DATA_POINT_VALUE_BYTE_SIZE_BS + TIMESTAMP_BS + DATA_POINTS_COUNT_BS

      uint256 timestampCalldataOffset = msg.data.length - 
        (calldataNegativeOffset + TIMESTAMP_NEGATIVE_OFFSET_IN_DATA_PACKAGE_WITH_STANDARD_SLOT_BS);

      uint256 signedMessageCalldataOffset = msg.data.length - 
        (calldataNegativeOffset + SIG_BS + signedMessageBytesCount);

      assembly {
        // Extracting the signed message
        signedMessage := extractBytesFromCalldata(
          signedMessageCalldataOffset,
          signedMessageBytesCount
        )

        // Hashing the signed message
        signedHash := keccak256(add(signedMessage, BYTES_ARR_LEN_VAR_BS), signedMessageBytesCount)

        // Extracting timestamp
        extractedTimestamp := calldataload(timestampCalldataOffset)

        function initByteArray(bytesCount) -> ptr {
          ptr := mload(FREE_MEMORY_PTR)
          mstore(ptr, bytesCount)
          ptr := add(ptr, BYTES_ARR_LEN_VAR_BS)
          mstore(FREE_MEMORY_PTR, add(ptr, bytesCount))
        }

        function extractBytesFromCalldata(offset, bytesCount) -> extractedBytes {
          let extractedBytesStartPtr := initByteArray(bytesCount)
          calldatacopy(
            extractedBytesStartPtr,
            offset,
            bytesCount
          )
          extractedBytes := sub(extractedBytesStartPtr, BYTES_ARR_LEN_VAR_BS)
        }
      }

      dataPackageTimestamp = extractedTimestamp;

      // Verifying the off-chain signature against on-chain hashed data
      signerAddress = SignatureLib.recoverSignerAddress(
        signedHash,
        calldataNegativeOffset + SIG_BS
      );
      signerIndex = getAuthorisedSignerIndex(signerAddress);
    }

    // Updating helpful arrays
    {
      calldataNegativeOffset = calldataNegativeOffset + DATA_PACKAGE_WITHOUT_DATA_POINTS_BS;
      bytes32 dataPointDataFeedId;
      uint256 dataPointValue;
      for (uint256 dataPointIndex = 0; dataPointIndex < dataPointsCount;) {
        calldataNegativeOffset = calldataNegativeOffset + eachDataPointValueByteSize + DATA_POINT_SYMBOL_BS;
        // Extracting data feed id and value for the current data point
        (dataPointDataFeedId, dataPointValue) = _extractDataPointValueAndDataFeedId(
          calldataNegativeOffset,
          eachDataPointValueByteSize
        );

        for (
          uint256 dataFeedIdIndex = 0;
          dataFeedIdIndex < dataFeedIds.length;
        ) {
          if (dataPointDataFeedId == dataFeedIds[dataFeedIdIndex]) {
            uint256 bitmapSignersForDataFeedId = signersBitmapForDataFeedIds[dataFeedIdIndex];

            if (
              !BitmapLib.getBitFromBitmap(bitmapSignersForDataFeedId, signerIndex) && /* current signer was not counted for current dataFeedId */
              uniqueSignerCountForDataFeedIds[dataFeedIdIndex] < getUniqueSignersThreshold()
            ) {
              // Add new value
              valuesForDataFeeds[dataFeedIdIndex][uniqueSignerCountForDataFeedIds[dataFeedIdIndex]] = dataPointValue;

              // Increase unique signer counter
              uniqueSignerCountForDataFeedIds[dataFeedIdIndex]++;

              // Update signers bitmap
              signersBitmapForDataFeedIds[dataFeedIdIndex] = BitmapLib.setBitInBitmap(
                bitmapSignersForDataFeedId,
                signerIndex
              );
            }

            // Breaking, as there couldn't be several indexes for the same feed ID
            break;
          }
          unchecked {
            dataFeedIdIndex++;
          }
        }
        unchecked {
           dataPointIndex++;
        }
      }
    }

    return (calldataNegativeOffset, dataPackageTimestamp);
  }

  /**
   * @dev This is a private helpful function, which aggregates values from different
   * authorised signers for the given arrays of values for each data feed
   *
   * @param valuesForDataFeeds 2-dimensional array, valuesForDataFeeds[i][j] contains
   * j-th value for the i-th data feed
   * @param uniqueSignerCountForDataFeedIds an array with the numbers of unique signers
   * for each data feed
   *
   * @return An array of the aggregated values
   */
  function _getAggregatedValues(
    uint256[][] memory valuesForDataFeeds,
    uint256[] memory uniqueSignerCountForDataFeedIds
  ) private view returns (uint256[] memory) {
    uint256[] memory aggregatedValues = new uint256[](valuesForDataFeeds.length);
    uint256 uniqueSignersThreshold = getUniqueSignersThreshold();

    for (uint256 dataFeedIndex = 0; dataFeedIndex < valuesForDataFeeds.length; dataFeedIndex++) {
      if (uniqueSignerCountForDataFeedIds[dataFeedIndex] < uniqueSignersThreshold) {
        revert InsufficientNumberOfUniqueSigners(
          uniqueSignerCountForDataFeedIds[dataFeedIndex],
          uniqueSignersThreshold);
      }
      uint256 aggregatedValueForDataFeedId = aggregateValues(valuesForDataFeeds[dataFeedIndex]);
      aggregatedValues[dataFeedIndex] = aggregatedValueForDataFeedId;
    }

    return aggregatedValues;
  }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import "./RedstoneConsumerBase.sol";

/**
 * @title The base contract for Redstone consumers' contracts that allows to
 * securely calculate numeric redstone oracle values
 * @author The Redstone Oracles team
 * @dev This contract can extend other contracts to allow them
 * securely fetch Redstone oracle data from transactions calldata
 */
abstract contract RedstoneConsumerNumericBase is RedstoneConsumerBase {
  /**
   * @dev This function can be used in a consumer contract to securely extract an
   * oracle value for a given data feed id. Security is achieved by
   * signatures verification, timestamp validation, and aggregating values
   * from different authorised signers into a single numeric value. If any of the
   * required conditions do not match, the function will revert.
   * Note! This function expects that tx calldata contains redstone payload in the end
   * Learn more about redstone payload here: https://github.com/redstone-finance/redstone-oracles-monorepo/tree/main/packages/evm-connector#readme
   * @param dataFeedId bytes32 value that uniquely identifies the data feed
   * @return Extracted and verified numeric oracle value for the given data feed id
   */
  function getOracleNumericValueFromTxMsg(bytes32 dataFeedId)
    internal
    view
    virtual
    returns (uint256)
  {
    bytes32[] memory dataFeedIds = new bytes32[](1);
    dataFeedIds[0] = dataFeedId;
    return getOracleNumericValuesFromTxMsg(dataFeedIds)[0];
  }

  /**
   * @dev This function can be used in a consumer contract to securely extract several
   * numeric oracle values for a given array of data feed ids. Security is achieved by
   * signatures verification, timestamp validation, and aggregating values
   * from different authorised signers into a single numeric value. If any of the
   * required conditions do not match, the function will revert.
   * Note! This function expects that tx calldata contains redstone payload in the end
   * Learn more about redstone payload here: https://github.com/redstone-finance/redstone-oracles-monorepo/tree/main/packages/evm-connector#readme
   * @param dataFeedIds An array of unique data feed identifiers
   * @return An array of the extracted and verified oracle values in the same order
   * as they are requested in the dataFeedIds array
   */
  function getOracleNumericValuesFromTxMsg(bytes32[] memory dataFeedIds)
    internal
    view
    virtual
    returns (uint256[] memory)
  {
    (uint256[] memory values, uint256 timestamp) = _securelyExtractOracleValuesAndTimestampFromTxMsg(dataFeedIds);
    validateTimestamp(timestamp);
    return values;
  }

  /**
   * @dev This function can be used in a consumer contract to securely extract several
   * numeric oracle values for a given array of data feed ids. Security is achieved by
   * signatures verification and aggregating values from different authorised signers 
   * into a single numeric value. If any of the required conditions do not match, 
   * the function will revert.
   * Note! This function returns the timestamp of the packages (it requires it to be 
   * the same for all), but does not validate this timestamp.
   * Note! This function expects that tx calldata contains redstone payload in the end
   * Learn more about redstone payload here: https://github.com/redstone-finance/redstone-oracles-monorepo/tree/main/packages/evm-connector#readme
   * @param dataFeedIds An array of unique data feed identifiers
   * @return An array of the extracted and verified oracle values in the same order
   * as they are requested in the dataFeedIds array and data packages timestamp
   */
   function getOracleNumericValuesAndTimestampFromTxMsg(bytes32[] memory dataFeedIds)
    internal
    view
    virtual
    returns (uint256[] memory, uint256)
  {
    return _securelyExtractOracleValuesAndTimestampFromTxMsg(dataFeedIds);
  }

  /**
   * @dev This function works similarly to the `getOracleNumericValuesFromTxMsg` with the
   * only difference that it allows to request oracle data for an array of data feeds
   * that may contain duplicates
   * 
   * @param dataFeedIdsWithDuplicates An array of data feed identifiers (duplicates are allowed)
   * @return An array of the extracted and verified oracle values in the same order
   * as they are requested in the dataFeedIdsWithDuplicates array
   */
  function getOracleNumericValuesWithDuplicatesFromTxMsg(bytes32[] memory dataFeedIdsWithDuplicates) internal view returns (uint256[] memory) {
    // Building an array without duplicates
    bytes32[] memory dataFeedIdsWithoutDuplicates = new bytes32[](dataFeedIdsWithDuplicates.length);
    bool alreadyIncluded;
    uint256 uniqueDataFeedIdsCount = 0;

    for (uint256 indexWithDup = 0; indexWithDup < dataFeedIdsWithDuplicates.length; indexWithDup++) {
      // Checking if current element is already included in `dataFeedIdsWithoutDuplicates`
      alreadyIncluded = false;
      for (uint256 indexWithoutDup = 0; indexWithoutDup < uniqueDataFeedIdsCount; indexWithoutDup++) {
        if (dataFeedIdsWithoutDuplicates[indexWithoutDup] == dataFeedIdsWithDuplicates[indexWithDup]) {
          alreadyIncluded = true;
          break;
        }
      }

      // Adding if not included
      if (!alreadyIncluded) {
        dataFeedIdsWithoutDuplicates[uniqueDataFeedIdsCount] = dataFeedIdsWithDuplicates[indexWithDup];
        uniqueDataFeedIdsCount++;
      }
    }

    // Overriding dataFeedIdsWithoutDuplicates.length
    // Equivalent to: dataFeedIdsWithoutDuplicates.length = uniqueDataFeedIdsCount;
    assembly {
      mstore(dataFeedIdsWithoutDuplicates, uniqueDataFeedIdsCount)
    }

    // Requesting oracle values (without duplicates)
    (uint256[] memory valuesWithoutDuplicates, uint256 timestamp) = _securelyExtractOracleValuesAndTimestampFromTxMsg(dataFeedIdsWithoutDuplicates);
    validateTimestamp(timestamp);

    // Preparing result values array
    uint256[] memory valuesWithDuplicates = new uint256[](dataFeedIdsWithDuplicates.length);
    for (uint256 indexWithDup = 0; indexWithDup < dataFeedIdsWithDuplicates.length; indexWithDup++) {
      for (uint256 indexWithoutDup = 0; indexWithoutDup < dataFeedIdsWithoutDuplicates.length; indexWithoutDup++) {
        if (dataFeedIdsWithDuplicates[indexWithDup] == dataFeedIdsWithoutDuplicates[indexWithoutDup]) {
          valuesWithDuplicates[indexWithDup] = valuesWithoutDuplicates[indexWithoutDup];
          break;
        }
      }
    }

    return valuesWithDuplicates;
  }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import "../libs/NumericArrayLib.sol";

/**
 * @title Default implementations of virtual redstone consumer base functions
 * @author The Redstone Oracles team
 */
library RedstoneDefaultsLib {
  uint256 constant DEFAULT_MAX_DATA_TIMESTAMP_DELAY_SECONDS = 3 minutes;
  uint256 constant DEFAULT_MAX_DATA_TIMESTAMP_AHEAD_SECONDS = 1 minutes;

  error TimestampFromTooLongFuture(uint256 receivedTimestampSeconds, uint256 blockTimestamp);
  error TimestampIsTooOld(uint256 receivedTimestampSeconds, uint256 blockTimestamp);

  function validateTimestamp(uint256 receivedTimestampMilliseconds) internal view {
    // Getting data timestamp from future seems quite unlikely
    // But we've already spent too much time with different cases
    // Where block.timestamp was less than dataPackage.timestamp.
    // Some blockchains may case this problem as well.
    // That's why we add MAX_BLOCK_TIMESTAMP_DELAY
    // and allow data "from future" but with a small delay
    uint256 receivedTimestampSeconds = receivedTimestampMilliseconds / 1000;

    if (block.timestamp < receivedTimestampSeconds) {
      if ((receivedTimestampSeconds - block.timestamp) > DEFAULT_MAX_DATA_TIMESTAMP_AHEAD_SECONDS) {
        revert TimestampFromTooLongFuture(receivedTimestampSeconds, block.timestamp);
      }
    } else if ((block.timestamp - receivedTimestampSeconds) > DEFAULT_MAX_DATA_TIMESTAMP_DELAY_SECONDS) {
      revert TimestampIsTooOld(receivedTimestampSeconds, block.timestamp);
    }
  }

  function aggregateValues(uint256[] memory values) internal pure returns (uint256) {
    return NumericArrayLib.pickMedian(values);
  }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library BitmapLib {
  function setBitInBitmap(uint256 bitmap, uint256 bitIndex) internal pure returns (uint256) {
    return bitmap | (1 << bitIndex);
  }

  function getBitFromBitmap(uint256 bitmap, uint256 bitIndex) internal pure returns (bool) {
    uint256 bitAtIndex = bitmap & (1 << bitIndex);
    return bitAtIndex > 0;
  }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library NumericArrayLib {
  // This function sort array in memory using bubble sort algorithm,
  // which performs even better than quick sort for small arrays

  uint256 constant BYTES_ARR_LEN_VAR_BS = 32;
  uint256 constant UINT256_VALUE_BS = 32;

  error CanNotPickMedianOfEmptyArray();

  // This function modifies the array
  function pickMedian(uint256[] memory arr) internal pure returns (uint256) {
    if (arr.length == 2) {
      return (arr[0] + arr[1]) / 2;
    }
    if (arr.length == 0) {
      revert CanNotPickMedianOfEmptyArray();
    }
    sort(arr);
    uint256 middleIndex = arr.length / 2;
    if (arr.length % 2 == 0) {
      uint256 sum = arr[middleIndex - 1] + arr[middleIndex];
      return sum / 2;
    } else {
      return arr[middleIndex];
    }
  }

  function sort(uint256[] memory arr) internal pure {
    assembly {
      let arrLength := mload(arr)
      let valuesPtr := add(arr, BYTES_ARR_LEN_VAR_BS)
      let endPtr := add(valuesPtr, mul(arrLength, UINT256_VALUE_BS))
      for {
        let arrIPtr := valuesPtr
      } lt(arrIPtr, endPtr) {
        arrIPtr := add(arrIPtr, UINT256_VALUE_BS) // arrIPtr += 32
      } {
        for {
          let arrJPtr := valuesPtr
        } lt(arrJPtr, arrIPtr) {
          arrJPtr := add(arrJPtr, UINT256_VALUE_BS) // arrJPtr += 32
        } {
          let arrI := mload(arrIPtr)
          let arrJ := mload(arrJPtr)
          if lt(arrI, arrJ) {
            mstore(arrIPtr, arrJ)
            mstore(arrJPtr, arrI)
          }
        }
      }
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library SignatureLib {
  uint256 constant ECDSA_SIG_R_BS = 32;
  uint256 constant ECDSA_SIG_S_BS = 32;

  error InvalidSignature(bytes32 signedHash);

  function recoverSignerAddress(bytes32 signedHash, uint256 signatureCalldataNegativeOffset)
    internal
    pure
    returns (address signerAddress)
  {
    bytes32 r;
    bytes32 s;
    uint8 v;
    assembly {
      let signatureCalldataStartPos := sub(calldatasize(), signatureCalldataNegativeOffset)
      r := calldataload(signatureCalldataStartPos)
      signatureCalldataStartPos := add(signatureCalldataStartPos, ECDSA_SIG_R_BS)
      s := calldataload(signatureCalldataStartPos)
      signatureCalldataStartPos := add(signatureCalldataStartPos, ECDSA_SIG_S_BS)
      v := byte(0, calldataload(signatureCalldataStartPos)) // last byte of the signature memory array
    }
    signerAddress = ecrecover(signedHash, v, r, s);
    if (signerAddress == address(0)) {
      revert InvalidSignature(signedHash);
    }
  }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

interface ILToken {
  function underlying() external view returns (address);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

interface IPriceCalculator {
  struct ReferenceData {
    uint256 lastData;
    uint256 lastUpdated;
  }

  function priceOf(address asset) external view returns (uint256);

  function pricesOf(
    address[] memory assets
  ) external view returns (uint256[] memory);

  function priceOfETH() external view returns (uint256);

  function getUnderlyingPrice(address gToken) external view returns (uint256);

  function getUnderlyingPrices(
    address[] memory gTokens
  ) external view returns (uint256[] memory);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import {MultiFeedAdapterWithoutRounds} from "../without-rounds/MultiFeedAdapterWithoutRounds.sol";

abstract contract MultiFeedAdapterWithoutRoundsPrimaryProd is MultiFeedAdapterWithoutRounds {
  function getUniqueSignersThreshold() public view virtual override returns (uint8) {
    return 3;
  }

  function getAuthorisedSignerIndex(
    address signerAddress
  ) public view virtual override returns (uint8) {
    if (signerAddress == 0x8BB8F32Df04c8b654987DAaeD53D6B6091e3B774) { return 0; }
    else if (signerAddress == 0xdEB22f54738d54976C4c0fe5ce6d408E40d88499) { return 1; }
    else if (signerAddress == 0x51Ce04Be4b3E32572C4Ec9135221d0691Ba7d202) { return 2; }
    else if (signerAddress == 0xDD682daEC5A90dD295d14DA4b0bec9281017b5bE) { return 3; }
    else if (signerAddress == 0x9c5AE89C4Af6aA32cE58588DBaF90d18a855B6de) { return 4; }
    else {
      revert SignerNotAuthorised(signerAddress);
    }
  }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

interface IMultiFeedAdapter {
  function updateDataFeedsValuesPartial(bytes32[] memory dataFeedsIds) external;

  function getLastUpdateDetails(bytes32 dataFeedId) external view returns (uint256 lastDataTimestamp, uint256 lastBlockTimestamp, uint256 lastValue);

  function getLastUpdateDetailsUnsafe(bytes32 dataFeedId) external view returns (uint256 lastDataTimestamp, uint256 lastBlockTimestamp, uint256 lastValue);

  function getValuesForDataFeeds(bytes32[] memory requestedDataFeedIds) external view returns (uint256[] memory values);

  function getValueForDataFeed(bytes32 dataFeedId) external view returns (uint256 dataFeedValue);

  function getDataTimestampFromLatestUpdate(bytes32 dataFeedId) external view returns (uint256 lastDataTimestamp);

  function getBlockTimestampFromLatestUpdate(bytes32 dataFeedId) external view returns (uint256 blockTimestamp);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import {RedstoneConsumerNumericBase, RedstoneDefaultsLib} from "@redstone-finance/evm-connector/contracts/core/RedstoneConsumerNumericBase.sol";
import {IMultiFeedAdapter} from "../interfaces/IMultiFeedAdapter.sol";
import {IPriceCalculator} from "../../custom-integrations/layerbank/IPriceCalculator.sol";
import {ILToken} from "../../custom-integrations/layerbank/ILToken.sol";

/**
 * @title MultiFeedAdapterWithoutRounds
 * @author The Redstone Oracles team
 * @dev This abstract contract serves as an adapter for multiple data feeds, facilitating
 * the updating and retrieval of oracle data values independently.
 *
 * Key details about the contract:
 * - Values for data feeds can be updated using the `updateDataFeedsValuesPartial` function
 * - Unlike the previous version (RedstoneAdapterBase), this adapter allows updating any set of data feeds,
 *   with each update being made independently.
 * - Updates are highly independent. Each data feed update is attempted separately, ensuring maximum possible
 *   updates without reverting the entire transaction if some of them fail. Both successful value updates and
 *   update skips due to failed validation are represented in corresponding events.
 * - Efficient storage usage: Related timestamps and values are packed into a single 32-byte slot when possible.
 *   If a value exceeds the slot capacity, it is stored in the next slot, with one bool prop (isValueBigger) indicating the storage method used.
 * - All data packages in the Redstone payload must have the same timestamp. Invalid timestamps (too old or too new) will cause transaction reversion.
 * - The contract includes a built-in IPriceCalculator interface used by LayerBank and other projects
 */
abstract contract MultiFeedAdapterWithoutRounds is RedstoneConsumerNumericBase, IMultiFeedAdapter, IPriceCalculator {
  bytes32 internal constant DATA_FEEDS_STORAGE_LOCATION = 0x5e9fb4cb0eb3c2583734d3394f30bb14b241acb9b3a034f7e7ba1a62db4370f1; // keccak256("RedStone.MultiFeedAdapterWithoutRounds.dataFeeds");
  bytes32 internal constant ETH_DATA_FEED_ID = bytes32("ETH");
  uint256 internal constant MAX_DATA_STALENESS = 30 hours;
  uint256 internal constant DEFAULT_DECIMAL_SCALER_LAYERBANK = 1e10;

  error DataTimestampTooLarge(uint256 dataTimestamp);
  error BlockTimestampTooLarge(uint256 blockTimestamp);
  error InvalidLastUpdateDetails(bytes32 dataFeedId, uint256 lastDataTimestamp, uint256 lastBlockTimestamp, uint256 lastValue);

  event ValueUpdate(uint256 value, bytes32 dataFeedId, uint256 updatedAt);
  event UpdateSkipDueToBlockTimestamp(bytes32 dataFeedId);
  event UpdateSkipDueToDataTimestamp(bytes32 dataFeedId);
  event UpdateSkipDueToInvalidValue(bytes32 dataFeedId);

  // This struct uses exactly one storage slot (32 bytes)
  struct DataFeedDetails {
    uint48 dataTimestamp;
    uint48 blockTimestamp;
    uint152 value;
    bool isValueBigger;
  }

  struct DataFeedDetailsWithOptionalBigValue {
    DataFeedDetails details;
    uint256 biggerValue;
  }

  // This struct is used only for returning values
  struct LastUpdateDetails {
    uint256 dataTimestamp;
    uint256 blockTimestamp;
    uint256 value;
  }

  struct DataFeedsStorage {
    mapping(bytes32 => DataFeedDetailsWithOptionalBigValue) _dataFeeds;
  }

  /// This function allows to update any set of data feeds
  function updateDataFeedsValuesPartial(bytes32[] memory dataFeedsIds) public {
    (uint256[] memory oracleValues, uint256 extractedDataTimestamp) = getOracleNumericValuesAndTimestampFromTxMsg(dataFeedsIds);

    // Revert if data timestamp doesn't fit within the allowed block timestamp window
    validateTimestamp(extractedDataTimestamp);

    // Revert if data or block timestamp do not fit into 48 bits reserved in storage for timestamps
    if (extractedDataTimestamp > type(uint48).max) {
      revert DataTimestampTooLarge(extractedDataTimestamp);
    }
    if (block.timestamp > type(uint48).max) {
      revert BlockTimestampTooLarge(block.timestamp);
    }

    // The logic below can fail only in the case when gas limit reached
    for (uint256 i = 0; i < dataFeedsIds.length;) {
      // Note, each update is independent. It means that we are trying to update everything we can.
      // And skip the rest (emitting corresponding events)
      _tryToUpdateDataFeed(dataFeedsIds[i], oracleValues[i], extractedDataTimestamp);
      unchecked { i++; } // reduces gas costs
    }
  }

  function _tryToUpdateDataFeed(bytes32 dataFeedId, uint256 value, uint256 dataTimestamp) internal virtual {
    (uint256 lastDataTimestamp, uint256 lastBlockTimestamp, uint256 lastValue) = getLastUpdateDetailsUnsafe(dataFeedId);

    if (!_validateBlockTimestamp(lastBlockTimestamp)) {
      emit UpdateSkipDueToBlockTimestamp(dataFeedId);
      return;
    }

    if (!_validateDataTimestamp(dataTimestamp, lastDataTimestamp)) {
      emit UpdateSkipDueToDataTimestamp(dataFeedId);
      return;
    }

    if (!_validateValueBeforeSave(dataFeedId, value, lastValue)) {
      emit UpdateSkipDueToInvalidValue(dataFeedId);
      return;
    }

    _saveNewUpdateDetails(dataFeedId, value, dataTimestamp);
    _emitEventAfterValueUpdate(dataFeedId, value);
  }

  function _saveNewUpdateDetails(bytes32 dataFeedId, uint256 newValue, uint256 dataTimestamp) internal {
    DataFeedDetailsWithOptionalBigValue storage dataFeed = _getDataFeedsStorage()._dataFeeds[dataFeedId];

    bool isValueBigger = newValue > type(uint152).max;

    // We can safely cast timestamps here, because we checked timestamp values in the `updateDataFeedsValuesPartial` function
    dataFeed.details = DataFeedDetails({
      dataTimestamp: uint48(dataTimestamp),
      blockTimestamp: uint48(block.timestamp),
      value: uint152(newValue), // we can store anything here is isValueBigger == true, but it's slightly cheaper to always store the same value
      isValueBigger: isValueBigger
    });

    if (isValueBigger) {
      dataFeed.biggerValue = newValue;
    }
  }

  function getLastUpdateDetails(bytes32 dataFeedId) public view virtual returns (uint256 lastDataTimestamp, uint256 lastBlockTimestamp, uint256 lastValue) {
    (lastDataTimestamp, lastBlockTimestamp, lastValue) = getLastUpdateDetailsUnsafe(dataFeedId);
    if (!_validateLastUpdateDetailsOnRead(dataFeedId, lastDataTimestamp, lastBlockTimestamp, lastValue)) {
      revert InvalidLastUpdateDetails(dataFeedId, lastDataTimestamp, lastBlockTimestamp, lastValue);
    }
  }

  function getLastUpdateDetailsUnsafe(bytes32 dataFeedId) public view virtual returns (uint256 lastDataTimestamp, uint256 lastBlockTimestamp, uint256 lastValue) {
    DataFeedDetailsWithOptionalBigValue storage dataFeed = _getDataFeedsStorage()._dataFeeds[dataFeedId];

    lastDataTimestamp = dataFeed.details.dataTimestamp;
    lastBlockTimestamp = dataFeed.details.blockTimestamp;

    if (dataFeed.details.isValueBigger) {
      lastValue = dataFeed.biggerValue;
    } else {
      lastValue = dataFeed.details.value;
    }
  }

  function _getDataFeedsStorage() private pure returns (DataFeedsStorage storage $) {
    assembly {
      $.slot := DATA_FEEDS_STORAGE_LOCATION
    }
  }

  /// This function can be used to implement time-based whitelisting (e.g. whitelisting for only X seconds after the latest update)
  /// Important! This function should not revert, it should only return bool result of the validation
  function _validateBlockTimestamp(uint256 lastBlockTimestamp) internal view virtual returns (bool) {
    // In the default implementation we just check if the block number is higher
    // To ensure max 1 update for a given data feed in a block
    return block.timestamp > lastBlockTimestamp;
  }

  /// Important! This function should not revert, it should only return bool result of the validation
  function _validateDataTimestamp(uint256 proposedDataTimestamp, uint256 lastDataTimestamp) internal view virtual returns (bool) {
    return proposedDataTimestamp > lastDataTimestamp;
  }

  /// Important! This function should not revert, it should only return bool result of the validation
  /// It can be overridden to handle more specific logic in future
  function _validateValueBeforeSave(bytes32 /* dataFeedId */, uint256 proposedValue, uint256 /* lastValue */) internal view virtual returns (bool) {
    return proposedValue > 0;
  }

  /// This function can be overridden (e.g. value validation and staleness check)
  /// We've added dataFeedId for being able to implement custom validation per feed
  function _validateLastUpdateDetailsOnRead(bytes32 /* dataFeedId */, uint256 /* lastDataTimestamp */, uint256 lastBlockTimestamp, uint256 lastValue) internal view virtual returns (bool) {
    return lastValue > 0 && lastBlockTimestamp + MAX_DATA_STALENESS > block.timestamp;
  }

  /// Important! This function should not revert, it should only emit an event
  /// It is a separate function, so that we can specify custom events for specific data feeds
  function _emitEventAfterValueUpdate(bytes32 dataFeedId, uint256 newValue) internal virtual {
    emit ValueUpdate(newValue, dataFeedId, block.timestamp);
  }


  ////////////////////////////////////////////////////
  /////////// Functions for relayers below ///////////
  ////////////////////////////////////////////////////

  function getLastUpdateDetailsUnsafeForMany(bytes32[] memory dataFeedIds) external view returns (LastUpdateDetails[] memory detailsForFeeds) {
    detailsForFeeds = new LastUpdateDetails[](dataFeedIds.length);
    for (uint256 i = 0; i < dataFeedIds.length;) {
      (detailsForFeeds[i].dataTimestamp, detailsForFeeds[i].blockTimestamp, detailsForFeeds[i].value) = getLastUpdateDetailsUnsafe(dataFeedIds[i]);
      unchecked { i++; } // reduces gas costs
    }
  }

  function getValuesForDataFeeds(bytes32[] memory requestedDataFeedIds) external view returns (uint256[] memory values) {
    values = new uint256[](requestedDataFeedIds.length);
    for (uint256 i = 0; i < requestedDataFeedIds.length;) {
      values[i] = getValueForDataFeed(requestedDataFeedIds[i]);
      unchecked { i++; } // reduces gas costs
    }
  }

  function getValueForDataFeed(bytes32 dataFeedId) public view virtual returns (uint256 dataFeedValue) {
    (,, dataFeedValue) = getLastUpdateDetails(dataFeedId);
  }

  function getDataTimestampFromLatestUpdate(bytes32 dataFeedId) external view virtual returns (uint256 lastDataTimestamp) {
    (lastDataTimestamp, ,) = getLastUpdateDetails(dataFeedId);
  }

  function getBlockTimestampFromLatestUpdate(bytes32 dataFeedId) external view virtual returns (uint256 blockTimestamp) {
    (, blockTimestamp, ) = getLastUpdateDetails(dataFeedId);
  }

  ///////////////////////////////////////////////////////
  //////////// LayerBank interface functions ////////////
  ///////////////////////////////////////////////////////

  /// We can connect manager contract here or implement it directly here
  /// By default, users will be able to use data feed identifiers (casted to addresses) in layerbank functions
  function getDataFeedIdForAsset(address asset) public view virtual returns(bytes32) {
    return bytes32(uint256(uint160(asset)));
  }

  function convertDecimals(bytes32 /* dataFeedId */, uint256 valueFromRedstonePayload) public view virtual returns (uint256) {
    return valueFromRedstonePayload * DEFAULT_DECIMAL_SCALER_LAYERBANK;
  }

  function getUnderlyingAsset(address gToken) public view virtual returns(address) {
    return ILToken(gToken).underlying();
  }

  function priceOf(address asset) public view virtual returns (uint256) {
    bytes32 dataFeedId = getDataFeedIdForAsset(asset);
    uint256 latestValue = getValueForDataFeed(dataFeedId);
    return convertDecimals(dataFeedId, latestValue);
  }

  function priceOfETH() public view virtual returns (uint256) {
    return convertDecimals(ETH_DATA_FEED_ID, getValueForDataFeed(ETH_DATA_FEED_ID));
  }

  function pricesOf(
    address[] memory assets
  ) external view returns (uint256[] memory values) {
    values = new uint256[](assets.length);
    for (uint256 i = 0; i < assets.length;) {
      values[i] = priceOf(assets[i]);
      unchecked { i++; } // reduces gas costs
    }
  }

  function getUnderlyingPrice(address gToken) public view returns (uint256) {
    return priceOf(getUnderlyingAsset(gToken));
  }

  function getUnderlyingPrices(
    address[] memory gTokens
  ) public view returns (uint256[] memory values) {
    values = new uint256[](gTokens.length);
    for (uint256 i = 0; i < gTokens.length;) {
      values[i] = getUnderlyingPrice(gTokens[i]);
      unchecked { i++; } // reduces gas costs
    }
  }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.4;

library OldGelatoAddress {
  address constant public ADDR = 0xc4D1AE5E796E6d7561cdc8335F85e6B57a36e097;
}

library GelatoAddress {
  address constant public ADDR = 0xCD6BfDA4D95d5C0f3f2882dC221D792392c99714;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

import {OldGelatoAddress, GelatoAddress} from "../__addresses/Addresses.sol";
import {MultiFeedAdapterWithoutRoundsPrimaryProd} from "@redstone-finance/on-chain-relayer/contracts/price-feeds/data-services/MultiFeedAdapterWithoutRoundsPrimaryProd.sol";

contract SonicMultiFeedAdapterWithoutRoundsV1 is MultiFeedAdapterWithoutRoundsPrimaryProd {

  address internal constant MAIN_UPDATER_ADDRESS = 0xF0547b3E44b904FeE2569ACf5107769dD28a17C3;
  address internal constant FALLBACK_UPDATER_ADDRESS = 0x3f11Cb12D6E69f66124ea6ca45D3C5ffDc4b1704;
  address internal constant MANUAL_UPDATER_ADDRESS = 0x4622DEcEfa557d7cFFd9aa00B93c5F9a7bbCe15f;

 function _validateBlockTimestamp(uint256 lastBlockTimestamp) internal view virtual override returns (bool) {  
    if (
      msg.sender == MAIN_UPDATER_ADDRESS ||
      msg.sender == FALLBACK_UPDATER_ADDRESS ||
      msg.sender == MANUAL_UPDATER_ADDRESS ||
      msg.sender == GelatoAddress.ADDR ||
      msg.sender == OldGelatoAddress.ADDR
    ) {  
      // For whitelisted addresses we only require a newer block  
      return block.timestamp > lastBlockTimestamp;  
    } else {  
      // For non-whitelisted addresses we require some time to pass after the latest update  
      return block.timestamp > lastBlockTimestamp + 40 seconds;  
    }  
  }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):