ETH Price: $2,571.44 (+1.93%)

Transaction Decoder

Block:
22854029 at Jul-05-2025 03:52:11 PM +UTC
Transaction Fee:
0.000172974977476645 ETH $0.44
Gas Used:
117,071 Gas / 1.477521995 Gwei

Emitted Events:

126 DataFeedsCache.BundleReportUpdated( dataId=System.Byte[], timestamp=1751730720, bundle=0x000000000000000000000000000000000000000000000000000B929071E5EC22 )
127 KeystoneForwarder.ReportProcessed( receiver=DataFeedsCache, workflowExecutionId=7962E1DE7C3094DD70E688260EB5EB548F1214A7E6BBCB7730FF3DCDF8681810, reportId=System.Byte[], result=True )

Account State Difference:

  Address   Before After State Difference Code
0x844FDF42...04b0a7C7e
0xa68A99C6...AE51c8d96
(BuilderNet)
29.339476078166635915 Eth29.339593149166635915 Eth0.000117071
0xef9CD5Ab...02Ee24B5D
2.30509911634948137 Eth
Nonce: 2238
2.304926141372004725 Eth
Nonce: 2239
0.000172974977476645

Execution Trace

KeystoneForwarder.report( receiver=0x844FDF4275F59ED011feF86857Db88404b0a7C7e, rawReport=0x017962E1DE7C3094DD70E688260EB5EB548F1214A7E6BBCB7730FF3DCDF868181068694A21000000040000000100FC71EEF9469F7C5D32CBE918349E8C69A3941DA9EC429A0C7F3A7B15CD998962366365336663613863FBB30BD8E9D779044C3C30DD82E52A5FA1573388000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002002CE8BFC980000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068694A2000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000B929071E5EC22, reportContext=0x000EC71B4C7561FCCDC37D6922E71EDD1E52F8C27D942D802A89FBCF060C504900000000000000000000000000000000000000000000000000000000467427000000000000000000000000000000000000000000000000000000000000000000, signatures=[lqRzISxuVkS+ZH2Co0J6JJWhxb9y1YhFuIedBiU632N30fN4FNV2aw9alRyMk4qTEztqVcWK/qVjkTlCpcRCxQA=, p4ueXkb/nJ6ED8anrEBBgy78OPQphDUDa6UttWPoRnppC/NPdAF5r0T4Y6kxKTzubnYv/luM9nBo0ADpTmuouAA=, iE7JdFPNAnWeajcwsMV1f2rzgPmhGum+Dx78uq84gBBNpOSSPWzObuhlIEC9EQ5Whqz/CYSX7nHhgybONrpM9AA=, Kcn+wpvCeG9YHaq6DhgfZkqRjqcOXqU1S8S/f/lQWL8SY4Oxw6H/HaVSWA23EAo/tRo7GkS2O0TTanHRyt4FIgA=] )
  • Null: 0x000...001.54a47cc5( )
  • Null: 0x000...001.54a47cc5( )
  • Null: 0x000...001.54a47cc5( )
  • Null: 0x000...001.54a47cc5( )
  • KeystoneForwarder.route( transmissionId=C84991F221D6290E3037B6EFA369527BCF4E021F364D63EE49A7E06AE52C198B, transmitter=0xef9CD5Ab3149e07f0EF928735Bf42c902Ee24B5D, receiver=0x844FDF4275F59ED011feF86857Db88404b0a7C7e, metadata=0x00FC71EEF9469F7C5D32CBE918349E8C69A3941DA9EC429A0C7F3A7B15CD998962366365336663613863FBB30BD8E9D779044C3C30DD82E52A5FA15733880001, validatedReport=0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002002CE8BFC980000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068694A2000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000B929071E5EC22 ) => ( True )
    • DataFeedsCache.supportsInterface( interfaceId=System.Byte[] ) => ( True )
    • DataFeedsCache.supportsInterface( interfaceId=System.Byte[] ) => ( False )
    • DataFeedsCache.supportsInterface( interfaceId=System.Byte[] ) => ( True )
    • DataFeedsCache.onReport( metadata=0x00FC71EEF9469F7C5D32CBE918349E8C69A3941DA9EC429A0C7F3A7B15CD998962366365336663613863FBB30BD8E9D779044C3C30DD82E52A5FA15733880001, report=0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002002CE8BFC980000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068694A2000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000B929071E5EC22 )
      File 1 of 2: KeystoneForwarder
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.19;
      import {IReceiver} from "./interfaces/IReceiver.sol";
      import {IRouter} from "./interfaces/IRouter.sol";
      import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
      import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol";
      import {ERC165Checker} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/ERC165Checker.sol";
      /// @notice This is an entry point for `write_${chain}` Target capability. It allows nodes to
      /// determine if reports have been processed (successfully or not) in a decentralized and
      /// product-agnostic way by recording processed reports.
      contract KeystoneForwarder is OwnerIsCreator, ITypeAndVersion, IRouter {
        /// @notice This error is returned when the report is shorter than REPORT_METADATA_LENGTH,
        /// which is the minimum length of a report.
        error InvalidReport();
        /// @notice This error is thrown whenever trying to set a config with a fault tolerance of 0.
        error FaultToleranceMustBePositive();
        /// @notice This error is thrown whenever configuration provides more signers than the maximum allowed number.
        /// @param numSigners The number of signers who have signed the report
        /// @param maxSigners The maximum number of signers that can sign a report
        error ExcessSigners(uint256 numSigners, uint256 maxSigners);
        /// @notice This error is thrown whenever a configuration is provided with less than the minimum number of signers.
        /// @param numSigners The number of signers provided
        /// @param minSigners The minimum number of signers expected
        error InsufficientSigners(uint256 numSigners, uint256 minSigners);
        /// @notice This error is thrown whenever a duplicate signer address is provided in the configuration.
        /// @param signer The signer address that was duplicated.
        error DuplicateSigner(address signer);
        /// @notice This error is thrown whenever a report has an incorrect number of signatures.
        /// @param expected The number of signatures expected, F + 1
        /// @param received The number of signatures received
        error InvalidSignatureCount(uint256 expected, uint256 received);
        /// @notice This error is thrown whenever a report specifies a configuration that does not exist.
        /// @param configId (uint64(donId) << 32) | configVersion
        error InvalidConfig(uint64 configId);
        /// @notice This error is thrown whenever a signer address is not in the configuration or
        /// when trying to set a zero address as a signer.
        /// @param signer The signer address that was not in the configuration
        error InvalidSigner(address signer);
        /// @notice This error is thrown whenever a signature is invalid.
        /// @param signature The signature that was invalid
        error InvalidSignature(bytes signature);
        /// @notice Contains the signing address of each oracle
        struct OracleSet {
          uint8 f; // Number of faulty nodes allowed
          address[] signers;
          mapping(address signer => uint256 position) _positions; // 1-indexed to detect unset values
        }
        struct Transmission {
          address transmitter;
          // This is true if the receiver is not a contract or does not implement the `IReceiver` interface.
          bool invalidReceiver;
          // Whether the transmission attempt was successful. If `false`, the transmission can be retried
          // with an increased gas limit.
          bool success;
          // The amount of gas allocated for the `IReceiver.onReport` call. uint80 allows storing gas for known EVM block
          // gas limits. Ensures that the minimum gas requested by the user is available during the transmission attempt.
          // If the transmission fails (indicated by a `false` success state), it can be retried with an increased gas limit.
          uint80 gasLimit;
        }
        /// @notice Emitted when a report is processed
        /// @param result The result of the attempted delivery. True if successful.
        event ReportProcessed(
          address indexed receiver,
          bytes32 indexed workflowExecutionId,
          bytes2 indexed reportId,
          bool result
        );
        /// @notice Contains the configuration for each DON ID
        /// configId (uint64(donId) << 32) | configVersion
        mapping(uint64 configId => OracleSet oracleSet) internal s_configs;
        event ConfigSet(uint32 indexed donId, uint32 indexed configVersion, uint8 f, address[] signers);
        string public constant override typeAndVersion = "KeystoneForwarder 1.0.0";
        constructor() OwnerIsCreator() {
          s_forwarders[address(this)] = true;
        }
        uint256 internal constant MAX_ORACLES = 31;
        uint256 internal constant METADATA_LENGTH = 109;
        uint256 internal constant FORWARDER_METADATA_LENGTH = 45;
        uint256 internal constant SIGNATURE_LENGTH = 65;
        /// @dev This is the gas required to store `success` after the report is processed.
        /// It is a warm storage write because of the packed struct. In practice it will cost less.
        uint256 internal constant INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT = 5_000;
        /// @dev This is the gas required to store the transmission struct and perform other checks.
        uint256 internal constant INTERNAL_GAS_REQUIREMENTS = 25_000 + INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT;
        /// @dev This is the minimum gas required to route a report. This includes internal gas requirements
        /// as well as the minimum gas that the user contract will receive. 30k * 3 gas is to account for
        /// cases where consumers need close to the 30k limit provided in the supportsInterface check.
        uint256 internal constant MINIMUM_GAS_LIMIT = INTERNAL_GAS_REQUIREMENTS + 30_000 * 3 + 10_000;
        // ================================================================
        // │                          Router                              │
        // ================================================================
        mapping(address forwarder => bool isForwarder) internal s_forwarders;
        mapping(bytes32 transmissionId => Transmission transmission) internal s_transmissions;
        function addForwarder(address forwarder) external onlyOwner {
          s_forwarders[forwarder] = true;
          emit ForwarderAdded(forwarder);
        }
        function removeForwarder(address forwarder) external onlyOwner {
          s_forwarders[forwarder] = false;
          emit ForwarderRemoved(forwarder);
        }
        function route(
          bytes32 transmissionId,
          address transmitter,
          address receiver,
          bytes calldata metadata,
          bytes calldata validatedReport
        ) public returns (bool) {
          if (!s_forwarders[msg.sender]) revert UnauthorizedForwarder();
          uint256 gasLimit = gasleft() - INTERNAL_GAS_REQUIREMENTS;
          if (gasLimit < MINIMUM_GAS_LIMIT) revert InsufficientGasForRouting(transmissionId);
          Transmission memory transmission = s_transmissions[transmissionId];
          if (transmission.success || transmission.invalidReceiver) revert AlreadyAttempted(transmissionId);
          s_transmissions[transmissionId].transmitter = transmitter;
          s_transmissions[transmissionId].gasLimit = uint80(gasLimit);
          // This call can consume up to 90k gas.
          if (!ERC165Checker.supportsInterface(receiver, type(IReceiver).interfaceId)) {
            s_transmissions[transmissionId].invalidReceiver = true;
            return false;
          }
          bool success;
          bytes memory payload = abi.encodeCall(IReceiver.onReport, (metadata, validatedReport));
          uint256 remainingGas = gasleft() - INTERNAL_GAS_REQUIREMENTS_AFTER_REPORT;
          assembly {
            // call and return whether we succeeded. ignore return data
            // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength)
            success := call(remainingGas, receiver, 0, add(payload, 0x20), mload(payload), 0x0, 0x0)
          }
          if (success) {
            s_transmissions[transmissionId].success = true;
          }
          return success;
        }
        function getTransmissionId(
          address receiver,
          bytes32 workflowExecutionId,
          bytes2 reportId
        ) public pure returns (bytes32) {
          // This is slightly cheaper compared to `keccak256(abi.encode(receiver, workflowExecutionId, reportId));`
          return keccak256(bytes.concat(bytes20(uint160(receiver)), workflowExecutionId, reportId));
        }
        function getTransmissionInfo(
          address receiver,
          bytes32 workflowExecutionId,
          bytes2 reportId
        ) external view returns (TransmissionInfo memory) {
          bytes32 transmissionId = getTransmissionId(receiver, workflowExecutionId, reportId);
          Transmission memory transmission = s_transmissions[transmissionId];
          TransmissionState state;
          if (transmission.transmitter == address(0)) {
            state = IRouter.TransmissionState.NOT_ATTEMPTED;
          } else if (transmission.invalidReceiver) {
            state = IRouter.TransmissionState.INVALID_RECEIVER;
          } else {
            state = transmission.success ? IRouter.TransmissionState.SUCCEEDED : IRouter.TransmissionState.FAILED;
          }
          return
            TransmissionInfo({
              gasLimit: transmission.gasLimit,
              invalidReceiver: transmission.invalidReceiver,
              state: state,
              success: transmission.success,
              transmissionId: transmissionId,
              transmitter: transmission.transmitter
            });
        }
        /// @notice Get transmitter of a given report or 0x0 if it wasn't transmitted yet
        function getTransmitter(
          address receiver,
          bytes32 workflowExecutionId,
          bytes2 reportId
        ) external view returns (address) {
          return s_transmissions[getTransmissionId(receiver, workflowExecutionId, reportId)].transmitter;
        }
        function isForwarder(address forwarder) external view returns (bool) {
          return s_forwarders[forwarder];
        }
        // ================================================================
        // │                          Forwarder                           │
        // ================================================================
        function setConfig(uint32 donId, uint32 configVersion, uint8 f, address[] calldata signers) external onlyOwner {
          if (f == 0) revert FaultToleranceMustBePositive();
          if (signers.length > MAX_ORACLES) revert ExcessSigners(signers.length, MAX_ORACLES);
          if (signers.length <= 3 * f) revert InsufficientSigners(signers.length, 3 * f + 1);
          uint64 configId = (uint64(donId) << 32) | configVersion;
          // remove any old signer addresses
          for (uint256 i = 0; i < s_configs[configId].signers.length; ++i) {
            delete s_configs[configId]._positions[s_configs[configId].signers[i]];
          }
          // add new signer addresses
          for (uint256 i = 0; i < signers.length; ++i) {
            // assign indices, detect duplicates
            address signer = signers[i];
            if (signer == address(0)) revert InvalidSigner(signer);
            if (s_configs[configId]._positions[signer] != 0) revert DuplicateSigner(signer);
            s_configs[configId]._positions[signer] = i + 1;
          }
          s_configs[configId].signers = signers;
          s_configs[configId].f = f;
          emit ConfigSet(donId, configVersion, f, signers);
        }
        function clearConfig(uint32 donId, uint32 configVersion) external onlyOwner {
          // We are not removing old signer positions, because it is sufficient to
          // clear the f value for `report` function. If we decide to restore
          // the configId in the future, the setConfig function clears the positions.
          s_configs[(uint64(donId) << 32) | configVersion].f = 0;
          emit ConfigSet(donId, configVersion, 0, new address[](0));
        }
        // send a report to receiver
        function report(
          address receiver,
          bytes calldata rawReport,
          bytes calldata reportContext,
          bytes[] calldata signatures
        ) external {
          if (rawReport.length < METADATA_LENGTH) {
            revert InvalidReport();
          }
          bytes32 workflowExecutionId;
          bytes2 reportId;
          {
            uint64 configId;
            (workflowExecutionId, configId, reportId) = _getMetadata(rawReport);
            OracleSet storage config = s_configs[configId];
            uint8 f = config.f;
            // f can never be 0, so this means the config doesn't actually exist
            if (f == 0) revert InvalidConfig(configId);
            if (f + 1 != signatures.length) revert InvalidSignatureCount(f + 1, signatures.length);
            // validate signatures
            bytes32 completeHash = keccak256(abi.encodePacked(keccak256(rawReport), reportContext));
            address[MAX_ORACLES + 1] memory signed;
            for (uint256 i = 0; i < signatures.length; ++i) {
              bytes calldata signature = signatures[i];
              if (signature.length != SIGNATURE_LENGTH) revert InvalidSignature(signature);
              address signer = ecrecover(
                completeHash,
                uint8(signature[64]) + 27,
                bytes32(signature[0:32]),
                bytes32(signature[32:64])
              );
              // validate signer is trusted and signature is unique
              uint256 index = config._positions[signer];
              if (index == 0) revert InvalidSigner(signer); // index is 1-indexed so we can detect unset signers
              if (signed[index] != address(0)) revert DuplicateSigner(signer);
              signed[index] = signer;
            }
          }
          bool success = this.route(
            getTransmissionId(receiver, workflowExecutionId, reportId),
            msg.sender,
            receiver,
            rawReport[FORWARDER_METADATA_LENGTH:METADATA_LENGTH],
            rawReport[METADATA_LENGTH:]
          );
          emit ReportProcessed(receiver, workflowExecutionId, reportId, success);
        }
        // solhint-disable-next-line chainlink-solidity/explicit-returns
        function _getMetadata(
          bytes memory rawReport
        ) internal pure returns (bytes32 workflowExecutionId, uint64 configId, bytes2 reportId) {
          // (first 32 bytes of memory contain length of the report)
          // version                offset  32, size  1
          // workflow_execution_id  offset  33, size 32
          // timestamp              offset  65, size  4
          // don_id                 offset  69, size  4
          // don_config_version,    offset  73, size  4
          // workflow_cid           offset  77, size 32
          // workflow_name          offset 109, size 10
          // workflow_owner         offset 119, size 20
          // report_id              offset 139, size  2
          assembly {
            workflowExecutionId := mload(add(rawReport, 33))
            // shift right by 24 bytes to get the combined don_id and don_config_version
            configId := shr(mul(24, 8), mload(add(rawReport, 69)))
            reportId := mload(add(rawReport, 139))
          }
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol";
      /// @title IReceiver - receives keystone reports
      /// @notice Implementations must support the IReceiver interface through ERC165.
      interface IReceiver is IERC165 {
        /// @notice Handles incoming keystone reports.
        /// @dev If this function call reverts, it can be retried with a higher gas
        /// limit. The receiver is responsible for discarding stale reports.
        /// @param metadata Report's metadata.
        /// @param report Workflow report.
        function onReport(bytes calldata metadata, bytes calldata report) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      /// @title IRouter - delivers keystone reports to receiver
      interface IRouter {
        error UnauthorizedForwarder();
        /// @dev Thrown when the gas limit is insufficient for handling state after
        /// calling the receiver function.
        error InsufficientGasForRouting(bytes32 transmissionId);
        error AlreadyAttempted(bytes32 transmissionId);
        event ForwarderAdded(address indexed forwarder);
        event ForwarderRemoved(address indexed forwarder);
        enum TransmissionState {
          NOT_ATTEMPTED,
          SUCCEEDED,
          INVALID_RECEIVER,
          FAILED
        }
        struct TransmissionInfo {
          bytes32 transmissionId;
          TransmissionState state;
          address transmitter;
          // This is true if the receiver is not a contract or does not implement the
          // `IReceiver` interface.
          bool invalidReceiver;
          // Whether the transmission attempt was successful. If `false`, the
          // transmission can be retried with an increased gas limit.
          bool success;
          // The amount of gas allocated for the `IReceiver.onReport` call. uint80
          // allows storing gas for known EVM block gas limits.
          // Ensures that the minimum gas requested by the user is available during
          // the transmission attempt. If the transmission fails (indicated by a
          // `false` success state), it can be retried with an increased gas limit.
          uint80 gasLimit;
        }
        function addForwarder(address forwarder) external;
        function removeForwarder(address forwarder) external;
        function route(
          bytes32 transmissionId,
          address transmitter,
          address receiver,
          bytes calldata metadata,
          bytes calldata report
        ) external returns (bool);
        function getTransmissionId(
          address receiver,
          bytes32 workflowExecutionId,
          bytes2 reportId
        ) external pure returns (bytes32);
        function getTransmissionInfo(
          address receiver,
          bytes32 workflowExecutionId,
          bytes2 reportId
        ) external view returns (TransmissionInfo memory);
        function getTransmitter(
          address receiver,
          bytes32 workflowExecutionId,
          bytes2 reportId
        ) external view returns (address);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface ITypeAndVersion {
        function typeAndVersion() external pure returns (string memory);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwner} from "./ConfirmedOwner.sol";
      /// @title The OwnerIsCreator contract
      /// @notice A contract with helpers for basic contract ownership.
      contract OwnerIsCreator is ConfirmedOwner {
        constructor() ConfirmedOwner(msg.sender) {}
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.2) (utils/introspection/ERC165Checker.sol)
      pragma solidity ^0.8.0;
      import "./IERC165.sol";
      /**
       * @dev Library used to query support of an interface declared via {IERC165}.
       *
       * Note that these functions return the actual result of the query: they do not
       * `revert` if an interface is not supported. It is up to the caller to decide
       * what to do in these cases.
       */
      library ERC165Checker {
          // As per the EIP-165 spec, no interface should ever match 0xffffffff
          bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
          /**
           * @dev Returns true if `account` supports the {IERC165} interface.
           */
          function supportsERC165(address account) internal view returns (bool) {
              // Any contract that implements ERC165 must explicitly indicate support of
              // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
              return
                  supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
                  !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
          }
          /**
           * @dev Returns true if `account` supports the interface defined by
           * `interfaceId`. Support for {IERC165} itself is queried automatically.
           *
           * See {IERC165-supportsInterface}.
           */
          function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
              // query support of both ERC165 as per the spec and support of _interfaceId
              return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
          }
          /**
           * @dev Returns a boolean array where each value corresponds to the
           * interfaces passed in and whether they're supported or not. This allows
           * you to batch check interfaces for a contract where your expectation
           * is that some interfaces may not be supported.
           *
           * See {IERC165-supportsInterface}.
           *
           * _Available since v3.4._
           */
          function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
              internal
              view
              returns (bool[] memory)
          {
              // an array of booleans corresponding to interfaceIds and whether they're supported or not
              bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
              // query support of ERC165 itself
              if (supportsERC165(account)) {
                  // query support of each interface in interfaceIds
                  for (uint256 i = 0; i < interfaceIds.length; i++) {
                      interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
                  }
              }
              return interfaceIdsSupported;
          }
          /**
           * @dev Returns true if `account` supports all the interfaces defined in
           * `interfaceIds`. Support for {IERC165} itself is queried automatically.
           *
           * Batch-querying can lead to gas savings by skipping repeated checks for
           * {IERC165} support.
           *
           * See {IERC165-supportsInterface}.
           */
          function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
              // query support of ERC165 itself
              if (!supportsERC165(account)) {
                  return false;
              }
              // query support of each interface in interfaceIds
              for (uint256 i = 0; i < interfaceIds.length; i++) {
                  if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                      return false;
                  }
              }
              // all interfaces supported
              return true;
          }
          /**
           * @notice Query if a contract implements an interface, does not check ERC165 support
           * @param account The address of the contract to query for support of an interface
           * @param interfaceId The interface identifier, as specified in ERC-165
           * @return true if the contract at account indicates support of the interface with
           * identifier interfaceId, false otherwise
           * @dev Assumes that account contains a contract that supports ERC165, otherwise
           * the behavior of this method is undefined. This precondition can be checked
           * with {supportsERC165}.
           *
           * Some precompiled contracts will falsely indicate support for a given interface, so caution
           * should be exercised when using this function.
           *
           * Interface identification is specified in ERC-165.
           */
          function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
              // prepare call
              bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
              // perform static call
              bool success;
              uint256 returnSize;
              uint256 returnValue;
              assembly {
                  success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
                  returnSize := returndatasize()
                  returnValue := mload(0x00)
              }
              return success && returnSize >= 0x20 && returnValue > 0;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
      pragma solidity ^0.8.20;
      /**
       * @dev Interface of the ERC165 standard, as defined in the
       * https://eips.ethereum.org/EIPS/eip-165[EIP].
       *
       * Implementers can declare support of contract interfaces, which can then be
       * queried by others ({ERC165Checker}).
       *
       * For an implementation, see {ERC165}.
       */
      interface IERC165 {
          /**
           * @dev Returns true if this contract implements the interface defined by
           * `interfaceId`. See the corresponding
           * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
           * to learn more about how these ids are created.
           *
           * This function call must use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwner is ConfirmedOwnerWithProposal {
        constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Interface of the ERC165 standard, as defined in the
       * https://eips.ethereum.org/EIPS/eip-165[EIP].
       *
       * Implementers can declare support of contract interfaces, which can then be
       * queried by others ({ERC165Checker}).
       *
       * For an implementation, see {ERC165}.
       */
      interface IERC165 {
          /**
           * @dev Returns true if this contract implements the interface defined by
           * `interfaceId`. See the corresponding
           * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
           * to learn more about how these ids are created.
           *
           * This function call must use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {IOwnable} from "../interfaces/IOwnable.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwnerWithProposal is IOwnable {
        address private s_owner;
        address private s_pendingOwner;
        event OwnershipTransferRequested(address indexed from, address indexed to);
        event OwnershipTransferred(address indexed from, address indexed to);
        constructor(address newOwner, address pendingOwner) {
          // solhint-disable-next-line gas-custom-errors
          require(newOwner != address(0), "Cannot set owner to zero");
          s_owner = newOwner;
          if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
          }
        }
        /// @notice Allows an owner to begin transferring ownership to a new address.
        function transferOwnership(address to) public override onlyOwner {
          _transferOwnership(to);
        }
        /// @notice Allows an ownership transfer to be completed by the recipient.
        function acceptOwnership() external override {
          // solhint-disable-next-line gas-custom-errors
          require(msg.sender == s_pendingOwner, "Must be proposed owner");
          address oldOwner = s_owner;
          s_owner = msg.sender;
          s_pendingOwner = address(0);
          emit OwnershipTransferred(oldOwner, msg.sender);
        }
        /// @notice Get the current owner
        function owner() public view override returns (address) {
          return s_owner;
        }
        /// @notice validate, transfer ownership, and emit relevant events
        function _transferOwnership(address to) private {
          // solhint-disable-next-line gas-custom-errors
          require(to != msg.sender, "Cannot transfer to self");
          s_pendingOwner = to;
          emit OwnershipTransferRequested(s_owner, to);
        }
        /// @notice validate access
        function _validateOwnership() internal view {
          // solhint-disable-next-line gas-custom-errors
          require(msg.sender == s_owner, "Only callable by owner");
        }
        /// @notice Reverts if called by anyone other than the contract owner.
        modifier onlyOwner() {
          _validateOwnership();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IOwnable {
        function owner() external returns (address);
        function transferOwnership(address recipient) external;
        function acceptOwnership() external;
      }
      

      File 2 of 2: DataFeedsCache
      // SPDX-License-Identifier: MIT
      pragma solidity 0.8.26;
      import {IReceiver} from "../keystone/interfaces/IReceiver.sol";
      import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol";
      import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
      import {IDataFeedsCache} from "./interfaces/IDataFeedsCache.sol";
      import {ITokenRecover} from "./interfaces/ITokenRecover.sol";
      import {IERC165} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol";
      import {IERC20} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/IERC20.sol";
      import {SafeERC20} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/utils/SafeERC20.sol";
      contract DataFeedsCache is IDataFeedsCache, IReceiver, ITokenRecover, ITypeAndVersion, OwnerIsCreator {
        using SafeERC20 for IERC20;
        string public constant override typeAndVersion = "DataFeedsCache 1.0.0";
        // solhint-disable-next-line
        uint256 public constant override version = 7;
        /// Cache State
        struct WorkflowMetadata {
          address allowedSender; // Address of the sender allowed to send new reports
          address allowedWorkflowOwner; // ─╮ Address of the workflow owner
          bytes10 allowedWorkflowName; // ──╯ Name of the workflow
        }
        struct FeedConfig {
          uint8[] bundleDecimals; // Only appliciable to Bundle reports - Decimal reports have decimals encoded into the DataId.
          string description; // Description of the feed (e.g. "LINK / USD")
          WorkflowMetadata[] workflowMetadata; // Metadata for the feed
        }
        struct ReceivedBundleReport {
          bytes32 dataId; // Data ID of the feed from the received report
          uint32 timestamp; // Timestamp of the feed from the received report
          bytes bundle; // Report data in raw bytes
        }
        struct ReceivedDecimalReport {
          bytes32 dataId; // Data ID of the feed from the received report
          uint32 timestamp; // ─╮ Timestamp of the feed from the received report
          uint224 answer; // ───╯  Report data in uint224
        }
        struct StoredBundleReport {
          bytes bundle; // The latest bundle report stored for a feed
          uint32 timestamp; // The timestamp of the latest bundle report
        }
        struct StoredDecimalReport {
          uint224 answer; // ───╮ The latest decimal report stored for a feed
          uint32 timestamp; // ─╯ The timestamp of the latest decimal report
        }
        /// The message sender determines which feed is being requested, as each proxy has a single associated feed
        mapping(address aggProxy => bytes16 dataId) private s_aggregatorProxyToDataId;
        /// The latest decimal reports for each decimal feed. This will always equal s_decimalReports[s_dataIdToRoundId[dataId]][dataId]
        mapping(bytes16 dataId => StoredDecimalReport) private s_latestDecimalReports;
        /// Decimal reports for each feed, per round
        mapping(uint256 roundId => mapping(bytes16 dataId => StoredDecimalReport)) private s_decimalReports;
        /// The latest bundle reports for each bundle feed
        mapping(bytes16 dataId => StoredBundleReport) private s_latestBundleReports;
        /// The latest round id for each feed
        mapping(bytes16 dataId => uint256 roundId) private s_dataIdToRoundId;
        /// Addresses that are permitted to configure all feeds
        mapping(address feedAdmin => bool isFeedAdmin) private s_feedAdmins;
        mapping(bytes16 dataId => FeedConfig) private s_feedConfigs;
        /// Whether a given Sender and Workflow have permission to write feed updates.
        /// reportHash is the keccak256 hash of the abi.encoded(dataId, sender, workflowOwner and workflowName)
        mapping(bytes32 reportHash => bool) private s_writePermissions;
        event BundleReportUpdated(bytes16 indexed dataId, uint256 indexed timestamp, bytes bundle);
        event DecimalReportUpdated(
          bytes16 indexed dataId, uint256 indexed roundId, uint256 indexed timestamp, uint224 answer
        );
        event DecimalFeedConfigSet(
          bytes16 indexed dataId, uint8 decimals, string description, WorkflowMetadata[] workflowMetadata
        );
        event BundleFeedConfigSet(
          bytes16 indexed dataId, uint8[] decimals, string description, WorkflowMetadata[] workflowMetadata
        );
        event FeedConfigRemoved(bytes16 indexed dataId);
        event TokenRecovered(address indexed token, address indexed to, uint256 amount);
        event FeedAdminSet(address indexed feedAdmin, bool indexed isAdmin);
        event ProxyDataIdRemoved(address indexed proxy, bytes16 indexed dataId);
        event ProxyDataIdUpdated(address indexed proxy, bytes16 indexed dataId);
        event InvalidUpdatePermission(bytes16 indexed dataId, address sender, address workflowOwner, bytes10 workflowName);
        event StaleDecimalReport(bytes16 indexed dataId, uint256 reportTimestamp, uint256 latestTimestamp);
        event StaleBundleReport(bytes16 indexed dataId, uint256 reportTimestamp, uint256 latestTimestamp);
        error ArrayLengthMismatch();
        error EmptyConfig();
        error ErrorSendingNative(address to, uint256 amount, bytes data);
        error FeedNotConfigured(bytes16 dataId);
        error InsufficientBalance(uint256 balance, uint256 requiredBalance);
        error InvalidAddress(address addr);
        error InvalidDataId();
        error InvalidWorkflowName(bytes10 workflowName);
        error UnauthorizedCaller(address caller);
        error NoMappingForSender(address proxy);
        modifier onlyFeedAdmin() {
          if (!s_feedAdmins[msg.sender]) revert UnauthorizedCaller(msg.sender);
          _;
        }
        /// @inheritdoc IERC165
        function supportsInterface(
          bytes4 interfaceId
        ) public pure returns (bool) {
          return (
            interfaceId == type(IDataFeedsCache).interfaceId || interfaceId == type(IERC165).interfaceId
              || interfaceId == type(IReceiver).interfaceId || interfaceId == type(ITokenRecover).interfaceId
              || interfaceId == type(ITypeAndVersion).interfaceId
          );
        }
        /// @notice Get the workflow metadata of a feed
        /// @param dataId data ID of the feed
        /// @param startIndex The cursor to start fetching the metadata from
        /// @param maxCount The number of metadata to fetch
        /// @return workflowMetadata The metadata of the feed
        function getFeedMetadata(
          bytes16 dataId,
          uint256 startIndex,
          uint256 maxCount
        ) external view returns (WorkflowMetadata[] memory workflowMetadata) {
          FeedConfig storage feedConfig = s_feedConfigs[dataId];
          uint256 workflowMetadataLength = feedConfig.workflowMetadata.length;
          if (workflowMetadataLength == 0) {
            revert FeedNotConfigured(dataId);
          }
          if (startIndex >= workflowMetadataLength) return new WorkflowMetadata[](0);
          uint256 endIndex = startIndex + maxCount;
          endIndex = endIndex > workflowMetadataLength || maxCount == 0 ? workflowMetadataLength : endIndex;
          workflowMetadata = new WorkflowMetadata[](endIndex - startIndex);
          for (uint256 idx; idx < workflowMetadata.length; idx++) {
            workflowMetadata[idx] = feedConfig.workflowMetadata[idx + startIndex];
          }
          return workflowMetadata;
        }
        /// @notice Checks to see if this data ID, msg.sender, workflow owner, and workflow name are permissioned
        /// @param dataId The data ID for the feed
        /// @param workflowMetadata workflow metadata
        function checkFeedPermission(
          bytes16 dataId,
          WorkflowMetadata memory workflowMetadata
        ) external view returns (bool hasPermission) {
          bytes32 permission = _createReportHash(
            dataId,
            workflowMetadata.allowedSender,
            workflowMetadata.allowedWorkflowOwner,
            workflowMetadata.allowedWorkflowName
          );
          return s_writePermissions[permission];
        }
        // ================================================================
        // │                  Contract Config Interface                   │
        // ================================================================
        /// @notice Initializes the config for a decimal feed
        /// @param dataIds The data IDs of the feeds to configure
        /// @param descriptions The descriptions of the feeds
        /// @param workflowMetadata List of workflow metadata (owners, senders, and names) for every feed
        function setDecimalFeedConfigs(
          bytes16[] calldata dataIds,
          string[] calldata descriptions,
          WorkflowMetadata[] calldata workflowMetadata
        ) external onlyFeedAdmin {
          if (workflowMetadata.length == 0 || dataIds.length == 0) {
            revert EmptyConfig();
          }
          if (dataIds.length != descriptions.length) {
            revert ArrayLengthMismatch();
          }
          for (uint256 i; i < dataIds.length; ++i) {
            bytes16 dataId = dataIds[i];
            if (dataId == bytes16(0)) revert InvalidDataId();
            FeedConfig storage feedConfig = s_feedConfigs[dataId];
            if (feedConfig.workflowMetadata.length > 0) {
              // Feed is already configured, remove the previous config
              for (uint256 j; j < feedConfig.workflowMetadata.length; ++j) {
                WorkflowMetadata memory feedCurrentWorkflowMetadata = feedConfig.workflowMetadata[j];
                bytes32 reportHash = _createReportHash(
                  dataId,
                  feedCurrentWorkflowMetadata.allowedSender,
                  feedCurrentWorkflowMetadata.allowedWorkflowOwner,
                  feedCurrentWorkflowMetadata.allowedWorkflowName
                );
                delete s_writePermissions[reportHash];
              }
              delete s_feedConfigs[dataId];
              emit FeedConfigRemoved(dataId);
            }
            for (uint256 j; j < workflowMetadata.length; ++j) {
              WorkflowMetadata memory feedWorkflowMetadata = workflowMetadata[j];
              // Do those checks only once for the first data id
              if (i == 0) {
                if (feedWorkflowMetadata.allowedSender == address(0)) {
                  revert InvalidAddress(feedWorkflowMetadata.allowedSender);
                }
                if (feedWorkflowMetadata.allowedWorkflowOwner == address(0)) {
                  revert InvalidAddress(feedWorkflowMetadata.allowedWorkflowOwner);
                }
                if (feedWorkflowMetadata.allowedWorkflowName == bytes10(0)) {
                  revert InvalidWorkflowName(feedWorkflowMetadata.allowedWorkflowName);
                }
              }
              bytes32 reportHash = _createReportHash(
                dataId,
                feedWorkflowMetadata.allowedSender,
                feedWorkflowMetadata.allowedWorkflowOwner,
                feedWorkflowMetadata.allowedWorkflowName
              );
              s_writePermissions[reportHash] = true;
              feedConfig.workflowMetadata.push(feedWorkflowMetadata);
            }
            feedConfig.description = descriptions[i];
            emit DecimalFeedConfigSet({
              dataId: dataId,
              decimals: _getDecimals(dataId),
              description: descriptions[i],
              workflowMetadata: workflowMetadata
            });
          }
        }
        /// @notice Initializes the config for a bundle feed
        /// @param dataIds The data IDs of the feeds to configure
        /// @param descriptions The descriptions of the feeds
        /// @param decimalsMatrix The number of decimals for each data point in the bundle for the feed
        /// @param workflowMetadata List of workflow metadata (owners, senders, and names) for every feed
        function setBundleFeedConfigs(
          bytes16[] calldata dataIds,
          string[] calldata descriptions,
          uint8[][] calldata decimalsMatrix,
          WorkflowMetadata[] calldata workflowMetadata
        ) external onlyFeedAdmin {
          if (workflowMetadata.length == 0 || dataIds.length == 0) {
            revert EmptyConfig();
          }
          if (dataIds.length != descriptions.length || dataIds.length != decimalsMatrix.length) {
            revert ArrayLengthMismatch();
          }
          for (uint256 i; i < dataIds.length; ++i) {
            bytes16 dataId = dataIds[i];
            if (dataId == bytes16(0)) revert InvalidDataId();
            FeedConfig storage feedConfig = s_feedConfigs[dataId];
            if (feedConfig.workflowMetadata.length > 0) {
              // Feed is already configured, remove the previous config
              for (uint256 j; j < feedConfig.workflowMetadata.length; ++j) {
                WorkflowMetadata memory feedCurrentWorkflowMetadata = feedConfig.workflowMetadata[j];
                bytes32 reportHash = _createReportHash(
                  dataId,
                  feedCurrentWorkflowMetadata.allowedSender,
                  feedCurrentWorkflowMetadata.allowedWorkflowOwner,
                  feedCurrentWorkflowMetadata.allowedWorkflowName
                );
                delete s_writePermissions[reportHash];
              }
              delete s_feedConfigs[dataId];
              emit FeedConfigRemoved(dataId);
            }
            for (uint256 j; j < workflowMetadata.length; ++j) {
              WorkflowMetadata memory feedWorkflowMetadata = workflowMetadata[j];
              // Do those checks only once for the first data id
              if (i == 0) {
                if (feedWorkflowMetadata.allowedSender == address(0)) {
                  revert InvalidAddress(feedWorkflowMetadata.allowedSender);
                }
                if (feedWorkflowMetadata.allowedWorkflowOwner == address(0)) {
                  revert InvalidAddress(feedWorkflowMetadata.allowedWorkflowOwner);
                }
                if (feedWorkflowMetadata.allowedWorkflowName == bytes10(0)) {
                  revert InvalidWorkflowName(feedWorkflowMetadata.allowedWorkflowName);
                }
              }
              bytes32 reportHash = _createReportHash(
                dataId,
                feedWorkflowMetadata.allowedSender,
                feedWorkflowMetadata.allowedWorkflowOwner,
                feedWorkflowMetadata.allowedWorkflowName
              );
              s_writePermissions[reportHash] = true;
              feedConfig.workflowMetadata.push(feedWorkflowMetadata);
            }
            feedConfig.bundleDecimals = decimalsMatrix[i];
            feedConfig.description = descriptions[i];
            emit BundleFeedConfigSet({
              dataId: dataId,
              decimals: decimalsMatrix[i],
              description: descriptions[i],
              workflowMetadata: workflowMetadata
            });
          }
        }
        /// @notice Removes feeds and all associated data, for a set of feeds
        /// @param dataIds And array of data IDs to delete the data and configs of
        function removeFeedConfigs(
          bytes16[] calldata dataIds
        ) external onlyFeedAdmin {
          for (uint256 i; i < dataIds.length; ++i) {
            bytes16 dataId = dataIds[i];
            if (s_feedConfigs[dataId].workflowMetadata.length == 0) revert FeedNotConfigured(dataId);
            for (uint256 j; j < s_feedConfigs[dataId].workflowMetadata.length; ++j) {
              WorkflowMetadata memory feedWorkflowMetadata = s_feedConfigs[dataId].workflowMetadata[j];
              bytes32 reportHash = _createReportHash(
                dataId,
                feedWorkflowMetadata.allowedSender,
                feedWorkflowMetadata.allowedWorkflowOwner,
                feedWorkflowMetadata.allowedWorkflowName
              );
              delete s_writePermissions[reportHash];
            }
            delete s_feedConfigs[dataId];
            emit FeedConfigRemoved(dataId);
          }
        }
        /// @notice Sets a feed admin for all feeds, only callable by the Owner
        /// @param feedAdmin The feed admin
        function setFeedAdmin(address feedAdmin, bool isAdmin) external onlyOwner {
          if (feedAdmin == address(0)) revert InvalidAddress(feedAdmin);
          s_feedAdmins[feedAdmin] = isAdmin;
          emit FeedAdminSet(feedAdmin, isAdmin);
        }
        /// @notice Returns a bool is an address has feed admin permission for all feeds
        /// @param feedAdmin The feed admin
        /// @return isFeedAdmin bool if the address is the feed admin for all feeds
        function isFeedAdmin(
          address feedAdmin
        ) external view returns (bool) {
          return s_feedAdmins[feedAdmin];
        }
        /// @inheritdoc IDataFeedsCache
        function updateDataIdMappingsForProxies(
          address[] calldata proxies,
          bytes16[] calldata dataIds
        ) external onlyFeedAdmin {
          uint256 numberOfProxies = proxies.length;
          if (numberOfProxies != dataIds.length) revert ArrayLengthMismatch();
          for (uint256 i; i < numberOfProxies; i++) {
            s_aggregatorProxyToDataId[proxies[i]] = dataIds[i];
            emit ProxyDataIdUpdated(proxies[i], dataIds[i]);
          }
        }
        /// @inheritdoc IDataFeedsCache
        function getDataIdForProxy(
          address proxy
        ) external view returns (bytes16 dataId) {
          return s_aggregatorProxyToDataId[proxy];
        }
        /// @inheritdoc IDataFeedsCache
        function removeDataIdMappingsForProxies(
          address[] calldata proxies
        ) external onlyFeedAdmin {
          uint256 numberOfProxies = proxies.length;
          for (uint256 i; i < numberOfProxies; i++) {
            address proxy = proxies[i];
            bytes16 dataId = s_aggregatorProxyToDataId[proxy];
            delete s_aggregatorProxyToDataId[proxy];
            emit ProxyDataIdRemoved(proxy, dataId);
          }
        }
        // ================================================================
        // │                   Token Transfer Interface                   │
        // ================================================================
        /// @inheritdoc ITokenRecover
        function recoverTokens(IERC20 token, address to, uint256 amount) external onlyOwner {
          if (address(token) == address(0)) {
            if (amount > address(this).balance) {
              revert InsufficientBalance(address(this).balance, amount);
            }
            (bool success, bytes memory data) = to.call{value: amount}("");
            if (!success) revert ErrorSendingNative(to, amount, data);
          } else {
            if (amount > token.balanceOf(address(this))) {
              revert InsufficientBalance(token.balanceOf(address(this)), amount);
            }
            token.safeTransfer(to, amount);
          }
          emit TokenRecovered(address(token), to, amount);
        }
        // ================================================================
        // │                    Cache Update Interface                    │
        // ================================================================
        /// @inheritdoc IReceiver
        function onReport(bytes calldata metadata, bytes calldata report) external {
          (address workflowOwner, bytes10 workflowName) = _getWorkflowMetaData(metadata);
          // The first 32 bytes is the offset to the array
          // The second 32 bytes is the length of the array
          uint256 numReports = uint256(bytes32(report[32:64]));
          // Decimal reports contain 96 bytes per report
          // The total length should equal to the sum of:
          // 32 bytes for the offset
          // 32 bytes for the number of reports
          // the number of reports times 96
          if (report.length == numReports * 96 + 64) {
            ReceivedDecimalReport[] memory decodedDecimalReports = abi.decode(report, (ReceivedDecimalReport[]));
            for (uint256 i; i < numReports; ++i) {
              ReceivedDecimalReport memory decodedDecimalReport = decodedDecimalReports[i];
              // single dataId can have multiple permissions, to be updated by multiple Workflows
              bytes16 dataId = bytes16(decodedDecimalReport.dataId);
              bytes32 permission = _createReportHash(dataId, msg.sender, workflowOwner, workflowName);
              if (!s_writePermissions[permission]) {
                emit InvalidUpdatePermission(dataId, msg.sender, workflowOwner, workflowName);
                continue;
              }
              if (decodedDecimalReport.timestamp <= s_latestDecimalReports[dataId].timestamp) {
                emit StaleDecimalReport(dataId, decodedDecimalReport.timestamp, s_latestDecimalReports[dataId].timestamp);
                continue;
              }
              StoredDecimalReport memory decimalReport =
                StoredDecimalReport({answer: decodedDecimalReport.answer, timestamp: decodedDecimalReport.timestamp});
              uint256 roundId = ++s_dataIdToRoundId[dataId];
              s_latestDecimalReports[dataId] = decimalReport;
              s_decimalReports[roundId][dataId] = decimalReport;
              emit DecimalReportUpdated(dataId, roundId, decimalReport.timestamp, decimalReport.answer);
              // Needed for DF1 backward compatibility
              emit NewRound(roundId, address(0), decodedDecimalReport.timestamp);
              emit AnswerUpdated(int256(uint256(decodedDecimalReport.answer)), roundId, block.timestamp);
            }
          }
          // Bundle reports contain more bytes for the offsets
          // The total length should equal to the sum of:
          // 32 bytes for the offset
          // 32 bytes for the number of reports
          // the number of reports times 224
          else {
            //For byte reports decode using ReceivedFeedReportBundle struct
            ReceivedBundleReport[] memory decodedBundleReports = abi.decode(report, (ReceivedBundleReport[]));
            for (uint256 i; i < decodedBundleReports.length; ++i) {
              ReceivedBundleReport memory decodedBundleReport = decodedBundleReports[i];
              bytes16 dataId = bytes16(decodedBundleReport.dataId);
              // same dataId can have multiple permissions
              bytes32 permission = _createReportHash(dataId, msg.sender, workflowOwner, workflowName);
              if (!s_writePermissions[permission]) {
                emit InvalidUpdatePermission(dataId, msg.sender, workflowOwner, workflowName);
                continue;
              }
              if (decodedBundleReport.timestamp <= s_latestBundleReports[dataId].timestamp) {
                emit StaleBundleReport(dataId, decodedBundleReport.timestamp, s_latestBundleReports[dataId].timestamp);
                continue;
              }
              StoredBundleReport memory bundleReport =
                StoredBundleReport({bundle: decodedBundleReport.bundle, timestamp: decodedBundleReport.timestamp});
              s_latestBundleReports[dataId] = bundleReport;
              emit BundleReportUpdated(dataId, bundleReport.timestamp, bundleReport.bundle);
            }
          }
        }
        // ================================================================
        // │                        Helper Methods                        │
        // ================================================================
        /// @notice Gets the Decimals of the feed from the data Id
        /// @param dataId The data ID for the feed
        /// @return feedDecimals The number of decimals the feed has
        function _getDecimals(
          bytes16 dataId
        ) internal pure returns (uint8 feedDecimals) {
          // Get the report type from data id. Report type has index of 7
          bytes1 reportType = _getDataType(dataId, 7);
          // For decimal reports convert to uint8, then shift
          if (reportType >= hex"20" && reportType <= hex"60") {
            return uint8(reportType) - 32;
          }
          // If not decimal type, return 0
          return 0;
        }
        /// @notice Extracts the workflow name and the workflow owner from the metadata parameter of onReport
        /// @param metadata The metadata in bytes format
        /// @return workflowOwner The owner of the workflow
        /// @return workflowName  The name of the workflow
        function _getWorkflowMetaData(
          bytes memory metadata
        ) internal pure returns (address, bytes10) {
          address workflowOwner;
          bytes10 workflowName;
          // (first 32 bytes contain length of the byte array)
          // workflow_cid             // offset 32, size 32
          // workflow_name            // offset 64, size 10
          // workflow_owner           // offset 74, size 20
          // report_name              // offset 94, size  2
          assembly {
            // no shifting needed for bytes10 type
            workflowName := mload(add(metadata, 64))
            // shift right by 12 bytes to get the actual value
            workflowOwner := shr(mul(12, 8), mload(add(metadata, 74)))
          }
          return (workflowOwner, workflowName);
        }
        /// @notice Extracts a byte from the data ID, to check data types
        /// @param dataId The data ID for the feed
        /// @param index The index of the byte to extract from the data Id
        /// @return dataType result The keccak256 hash of the abi.encoded inputs
        function _getDataType(bytes16 dataId, uint256 index) internal pure returns (bytes1 dataType) {
          // Convert bytes16 to bytes
          return abi.encodePacked(dataId)[index];
        }
        /// @notice Creates a report hash used to permission write access
        /// @param dataId The data ID for the feed
        /// @param sender The msg.sender of the transaction calling into onReport
        /// @param workflowOwner The owner of the workflow
        /// @param workflowName The name of the workflow
        /// @return reportHash The keccak256 hash of the abi.encoded inputs
        function _createReportHash(
          bytes16 dataId,
          address sender,
          address workflowOwner,
          bytes10 workflowName
        ) internal pure returns (bytes32) {
          return keccak256(abi.encode(dataId, sender, workflowOwner, workflowName));
        }
        // ================================================================
        // │                    Data Access Interface                     │
        // ================================================================
        /// Bundle Feed Interface
        function latestBundle() external view returns (bytes memory bundle) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return (s_latestBundleReports[dataId].bundle);
        }
        function bundleDecimals() external view returns (uint8[] memory bundleFeedDecimals) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return s_feedConfigs[dataId].bundleDecimals;
        }
        function latestBundleTimestamp() external view returns (uint256 timestamp) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return s_latestBundleReports[dataId].timestamp;
        }
        /// AggregatorInterface
        function latestAnswer() external view returns (int256 answer) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return int256(uint256(s_latestDecimalReports[dataId].answer));
        }
        function latestTimestamp() external view returns (uint256 timestamp) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return s_latestDecimalReports[dataId].timestamp;
        }
        function latestRound() external view returns (uint256 round) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return s_dataIdToRoundId[dataId];
        }
        function getAnswer(
          uint256 roundId
        ) external view returns (int256 answer) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return int256(uint256(s_decimalReports[roundId][dataId].answer));
        }
        function getTimestamp(
          uint256 roundId
        ) external view returns (uint256 timestamp) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return s_decimalReports[roundId][dataId].timestamp;
        }
        /// AggregatorV3Interface
        function decimals() external view returns (uint8 feedDecimals) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return _getDecimals(dataId);
        }
        function description() external view returns (string memory feedDescription) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          return s_feedConfigs[dataId].description;
        }
        function getRoundData(
          uint80 roundId
        ) external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          uint256 timestamp = s_decimalReports[uint256(roundId)][dataId].timestamp;
          return (roundId, int256(uint256(s_decimalReports[uint256(roundId)][dataId].answer)), timestamp, timestamp, roundId);
        }
        function latestRoundData()
          external
          view
          returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
        {
          bytes16 dataId = s_aggregatorProxyToDataId[msg.sender];
          if (dataId == bytes16(0)) revert NoMappingForSender(msg.sender);
          uint80 roundId = uint80(s_dataIdToRoundId[dataId]);
          uint256 timestamp = s_latestDecimalReports[dataId].timestamp;
          return (roundId, int256(uint256(s_latestDecimalReports[dataId].answer)), timestamp, timestamp, roundId);
        }
        /// Direct access
        function getLatestBundle(
          bytes16 dataId
        ) external view returns (bytes memory bundle) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return (s_latestBundleReports[dataId].bundle);
        }
        function getBundleDecimals(
          bytes16 dataId
        ) external view returns (uint8[] memory bundleFeedDecimals) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return s_feedConfigs[dataId].bundleDecimals;
        }
        function getLatestBundleTimestamp(
          bytes16 dataId
        ) external view returns (uint256 timestamp) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return s_latestBundleReports[dataId].timestamp;
        }
        function getLatestAnswer(
          bytes16 dataId
        ) external view returns (int256 answer) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return int256(uint256(s_latestDecimalReports[dataId].answer));
        }
        function getLatestTimestamp(
          bytes16 dataId
        ) external view returns (uint256 timestamp) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return s_latestDecimalReports[dataId].timestamp;
        }
        function getLatestRoundData(
          bytes16 dataId
        ) external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          uint80 roundId = uint80(s_dataIdToRoundId[dataId]);
          uint256 timestamp = s_latestDecimalReports[dataId].timestamp;
          return (roundId, int256(uint256(s_latestDecimalReports[dataId].answer)), timestamp, timestamp, roundId);
        }
        function getDecimals(
          bytes16 dataId
        ) external pure returns (uint8 feedDecimals) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return _getDecimals(dataId);
        }
        function getDescription(
          bytes16 dataId
        ) external view returns (string memory feedDescription) {
          if (dataId == bytes16(0)) revert InvalidDataId();
          return s_feedConfigs[dataId].description;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol";
      /// @title IReceiver - receives keystone reports
      /// @notice Implementations must support the IReceiver interface through ERC165.
      interface IReceiver is IERC165 {
        /// @notice Handles incoming keystone reports.
        /// @dev If this function call reverts, it can be retried with a higher gas
        /// limit. The receiver is responsible for discarding stale reports.
        /// @param metadata Report's metadata.
        /// @param report Workflow report.
        function onReport(bytes calldata metadata, bytes calldata report) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwner} from "./ConfirmedOwner.sol";
      /// @title The OwnerIsCreator contract
      /// @notice A contract with helpers for basic contract ownership.
      contract OwnerIsCreator is ConfirmedOwner {
        constructor() ConfirmedOwner(msg.sender) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface ITypeAndVersion {
        function typeAndVersion() external pure returns (string memory);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import {IBundleBaseAggregator} from "./IBundleBaseAggregator.sol";
      import {ICommonAggregator} from "./ICommonAggregator.sol";
      import {IDecimalAggregator} from "./IDecimalAggregator.sol";
      /// @notice IDataFeedsCache
      /// Responsible for storing data associated with a given data ID and additional request data.
      interface IDataFeedsCache is IDecimalAggregator, IBundleBaseAggregator, ICommonAggregator {
        /// @notice Remove feed configs.
        /// @param dataIds List of data IDs
        function removeFeedConfigs(
          bytes16[] calldata dataIds
        ) external;
        /// @notice Update mappings for AggregatorProxy -> Data ID
        /// @param proxies AggregatorProxy addresses
        /// @param dataIds Data IDs
        function updateDataIdMappingsForProxies(address[] calldata proxies, bytes16[] calldata dataIds) external;
        /// @notice Remove mappings for AggregatorProxy -> Data IDs
        /// @param proxies  AggregatorProxy addresses to remove
        function removeDataIdMappingsForProxies(
          address[] calldata proxies
        ) external;
        /// @notice Get the Data ID mapping for a AggregatorProxy
        /// @param proxy AggregatorProxy addresses which will be reading feed data
        function getDataIdForProxy(
          address proxy
        ) external view returns (bytes16 dataId);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.20;
      import {IERC20} from "./../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC20.sol";
      /// @notice ITokenRecover
      /// Implements the recoverTokens method, enabling the recovery of ERC-20 or native tokens accidentally sent to a
      /// contract outside of normal operations.
      interface ITokenRecover {
        /// @notice Transfer any ERC-20 or native tokens accidentally sent to this contract.
        /// @param token Token to transfer
        /// @param to Address to send payment to
        /// @param amount Amount of token to transfer
        function recoverTokens(IERC20 token, address to, uint256 amount) external;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
      pragma solidity ^0.8.20;
      import {IERC165} from "../utils/introspection/IERC165.sol";
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
      pragma solidity ^0.8.20;
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @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);
          /**
           * @dev Returns the value of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
          /**
           * @dev Returns the value of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
          /**
           * @dev Moves a `value` amount of tokens from the caller's account to `to`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool);
          /**
           * @dev Moves a `value` amount of tokens from `from` to `to` using the
           * allowance mechanism. `value` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(address from, address to, uint256 value) external returns (bool);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
      pragma solidity ^0.8.20;
      import {IERC20} from "../IERC20.sol";
      import {IERC20Permit} from "../extensions/IERC20Permit.sol";
      import {Address} from "../../../utils/Address.sol";
      /**
       * @title SafeERC20
       * @dev Wrappers around ERC20 operations that throw on failure (when the token
       * contract returns false). Tokens that return no value (and instead revert or
       * throw on failure) are also supported, non-reverting calls are assumed to be
       * successful.
       * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
       * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
       */
      library SafeERC20 {
          using Address for address;
          /**
           * @dev An operation with an ERC20 token failed.
           */
          error SafeERC20FailedOperation(address token);
          /**
           * @dev Indicates a failed `decreaseAllowance` request.
           */
          error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
          /**
           * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
           * non-reverting calls are assumed to be successful.
           */
          function safeTransfer(IERC20 token, address to, uint256 value) internal {
              _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
          }
          /**
           * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
           * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
           */
          function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
              _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
          }
          /**
           * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
           * non-reverting calls are assumed to be successful.
           */
          function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 oldAllowance = token.allowance(address(this), spender);
              forceApprove(token, spender, oldAllowance + value);
          }
          /**
           * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
           * value, non-reverting calls are assumed to be successful.
           */
          function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
              unchecked {
                  uint256 currentAllowance = token.allowance(address(this), spender);
                  if (currentAllowance < requestedDecrease) {
                      revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
                  }
                  forceApprove(token, spender, currentAllowance - requestedDecrease);
              }
          }
          /**
           * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
           * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
           * to be set to zero before setting it to a non-zero value, such as USDT.
           */
          function forceApprove(IERC20 token, address spender, uint256 value) internal {
              bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
              if (!_callOptionalReturnBool(token, approvalCall)) {
                  _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
                  _callOptionalReturn(token, approvalCall);
              }
          }
          /**
           * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
           * on the return value: the return value is optional (but if data is returned, it must not be false).
           * @param token The token targeted by the call.
           * @param data The call data (encoded using abi.encode or one of its variants).
           */
          function _callOptionalReturn(IERC20 token, bytes memory data) private {
              // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
              // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
              // the target address contains contract code and also asserts for success in the low-level call.
              bytes memory returndata = address(token).functionCall(data);
              if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
                  revert SafeERC20FailedOperation(address(token));
              }
          }
          /**
           * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
           * on the return value: the return value is optional (but if data is returned, it must not be false).
           * @param token The token targeted by the call.
           * @param data The call data (encoded using abi.encode or one of its variants).
           *
           * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
           */
          function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
              // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
              // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
              // and not revert is the subcall reverts.
              (bool success, bytes memory returndata) = address(token).call(data);
              return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
      pragma solidity ^0.8.20;
      /**
       * @dev Interface of the ERC165 standard, as defined in the
       * https://eips.ethereum.org/EIPS/eip-165[EIP].
       *
       * Implementers can declare support of contract interfaces, which can then be
       * queried by others ({ERC165Checker}).
       *
       * For an implementation, see {ERC165}.
       */
      interface IERC165 {
          /**
           * @dev Returns true if this contract implements the interface defined by
           * `interfaceId`. See the corresponding
           * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
           * to learn more about how these ids are created.
           *
           * This function call must use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwner is ConfirmedOwnerWithProposal {
        constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      interface IBundleBaseAggregator {
        function latestBundle() external view returns (bytes memory bundle);
        function bundleDecimals() external view returns (uint8[] memory);
        function latestBundleTimestamp() external view returns (uint256);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      interface ICommonAggregator {
        function description() external view returns (string memory);
        function version() external view returns (uint256);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      interface IDecimalAggregator {
        function latestAnswer() external view returns (int256);
        function latestRound() external view returns (uint256);
        function latestTimestamp() external view returns (uint256);
        function getAnswer(
          uint256 roundId
        ) external view returns (int256);
        function getTimestamp(
          uint256 roundId
        ) external view returns (uint256);
        function decimals() external view returns (uint8);
        function getRoundData(
          uint80 _roundId
        ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
        function latestRoundData()
          external
          view
          returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
        event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);
        event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
      pragma solidity ^0.8.20;
      import {IERC20} from "../token/ERC20/IERC20.sol";
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
      pragma solidity ^0.8.20;
      /**
       * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
       * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
       *
       * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
       * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
       * need to send a transaction, and thus is not required to hold Ether at all.
       *
       * ==== Security Considerations
       *
       * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
       * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
       * considered as an intention to spend the allowance in any specific way. The second is that because permits have
       * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
       * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
       * generally recommended is:
       *
       * ```solidity
       * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
       *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
       *     doThing(..., value);
       * }
       *
       * function doThing(..., uint256 value) public {
       *     token.safeTransferFrom(msg.sender, address(this), value);
       *     ...
       * }
       * ```
       *
       * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
       * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
       * {SafeERC20-safeTransferFrom}).
       *
       * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
       * contracts should have entry points that don't rely on permit.
       */
      interface IERC20Permit {
          /**
           * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
           * given ``owner``'s signed approval.
           *
           * IMPORTANT: The same issues {IERC20-approve} has related to transaction
           * ordering also apply here.
           *
           * Emits an {Approval} event.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           * - `deadline` must be a timestamp in the future.
           * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
           * over the EIP712-formatted function arguments.
           * - the signature must use ``owner``'s current nonce (see {nonces}).
           *
           * For more information on the signature format, see the
           * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
           * section].
           *
           * CAUTION: See Security Considerations above.
           */
          function permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external;
          /**
           * @dev Returns the current nonce for `owner`. This value must be
           * included whenever a signature is generated for {permit}.
           *
           * Every successful call to {permit} increases ``owner``'s nonce by one. This
           * prevents a signature from being used multiple times.
           */
          function nonces(address owner) external view returns (uint256);
          /**
           * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
           */
          // solhint-disable-next-line func-name-mixedcase
          function DOMAIN_SEPARATOR() external view returns (bytes32);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
      pragma solidity ^0.8.20;
      /**
       * @dev Collection of functions related to the address type
       */
      library Address {
          /**
           * @dev The ETH balance of the account is not enough to perform the operation.
           */
          error AddressInsufficientBalance(address account);
          /**
           * @dev There's no code at `target` (it is not a contract).
           */
          error AddressEmptyCode(address target);
          /**
           * @dev A call to an address target failed. The target may have reverted.
           */
          error FailedInnerCall();
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              if (address(this).balance < amount) {
                  revert AddressInsufficientBalance(address(this));
              }
              (bool success, ) = recipient.call{value: amount}("");
              if (!success) {
                  revert FailedInnerCall();
              }
          }
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain `call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason or custom error, it is bubbled
           * up by this function (like regular Solidity function calls). However, if
           * the call reverted with no returned reason, this function reverts with a
           * {FailedInnerCall} error.
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           */
          function functionCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           */
          function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
              if (address(this).balance < value) {
                  revert AddressInsufficientBalance(address(this));
              }
              (bool success, bytes memory returndata) = target.call{value: value}(data);
              return verifyCallResultFromTarget(target, success, returndata);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a static call.
           */
          function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
              (bool success, bytes memory returndata) = target.staticcall(data);
              return verifyCallResultFromTarget(target, success, returndata);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a delegate call.
           */
          function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
              (bool success, bytes memory returndata) = target.delegatecall(data);
              return verifyCallResultFromTarget(target, success, returndata);
          }
          /**
           * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
           * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
           * unsuccessful call.
           */
          function verifyCallResultFromTarget(
              address target,
              bool success,
              bytes memory returndata
          ) internal view returns (bytes memory) {
              if (!success) {
                  _revert(returndata);
              } else {
                  // only check if target is a contract if the call was successful and the return data is empty
                  // otherwise we already know that it was a contract
                  if (returndata.length == 0 && target.code.length == 0) {
                      revert AddressEmptyCode(target);
                  }
                  return returndata;
              }
          }
          /**
           * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
           * revert reason or with a default {FailedInnerCall} error.
           */
          function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
              if (!success) {
                  _revert(returndata);
              } else {
                  return returndata;
              }
          }
          /**
           * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
           */
          function _revert(bytes memory returndata) private pure {
              // Look for revert reason and bubble it up if present
              if (returndata.length > 0) {
                  // The easiest way to bubble the revert reason is using memory via assembly
                  /// @solidity memory-safe-assembly
                  assembly {
                      let returndata_size := mload(returndata)
                      revert(add(32, returndata), returndata_size)
                  }
              } else {
                  revert FailedInnerCall();
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import {IOwnable} from "../interfaces/IOwnable.sol";
      /// @title The ConfirmedOwner contract
      /// @notice A contract with helpers for basic contract ownership.
      contract ConfirmedOwnerWithProposal is IOwnable {
        address private s_owner;
        address private s_pendingOwner;
        event OwnershipTransferRequested(address indexed from, address indexed to);
        event OwnershipTransferred(address indexed from, address indexed to);
        constructor(address newOwner, address pendingOwner) {
          // solhint-disable-next-line gas-custom-errors
          require(newOwner != address(0), "Cannot set owner to zero");
          s_owner = newOwner;
          if (pendingOwner != address(0)) {
            _transferOwnership(pendingOwner);
          }
        }
        /// @notice Allows an owner to begin transferring ownership to a new address.
        function transferOwnership(address to) public override onlyOwner {
          _transferOwnership(to);
        }
        /// @notice Allows an ownership transfer to be completed by the recipient.
        function acceptOwnership() external override {
          // solhint-disable-next-line gas-custom-errors
          require(msg.sender == s_pendingOwner, "Must be proposed owner");
          address oldOwner = s_owner;
          s_owner = msg.sender;
          s_pendingOwner = address(0);
          emit OwnershipTransferred(oldOwner, msg.sender);
        }
        /// @notice Get the current owner
        function owner() public view override returns (address) {
          return s_owner;
        }
        /// @notice validate, transfer ownership, and emit relevant events
        function _transferOwnership(address to) private {
          // solhint-disable-next-line gas-custom-errors
          require(to != msg.sender, "Cannot transfer to self");
          s_pendingOwner = to;
          emit OwnershipTransferRequested(s_owner, to);
        }
        /// @notice validate access
        function _validateOwnership() internal view {
          // solhint-disable-next-line gas-custom-errors
          require(msg.sender == s_owner, "Only callable by owner");
        }
        /// @notice Reverts if called by anyone other than the contract owner.
        modifier onlyOwner() {
          _validateOwnership();
          _;
        }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IOwnable {
        function owner() external returns (address);
        function transferOwnership(address recipient) external;
        function acceptOwnership() external;
      }