diff --git a/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistry.sol b/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistry.sol index 1aaf910b981..0b665e72c79 100644 --- a/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistry.sol +++ b/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistry.sol @@ -28,208 +28,210 @@ import "../../../ChainSpecificUtil.sol"; | - Optimize gas consumption. | -+---------------------------------------------------------------------------------------------------------------------*/ contract MercuryRegistry is AutomationCompatibleInterface, FeedLookupCompatibleInterface { - // Feed object used for storing feed data. - // not included but contained in reports: - // - blocknumberUpperBound - // - upperBlockhash - // - blocknumberLowerBound - // - currentBlockTimestamp - struct Feed { - uint32 observationsTimestamp; // the timestamp of the most recent data assigned to this feed - int192 price; // the current price of the feed - int192 bid; // the current bid price of the feed - int192 ask; // the current ask price of the feed - string feedName; // the name of the feed - string feedId; // the id of the feed (hex encoded) + // Feed object used for storing feed data. + // not included but contained in reports: + // - blocknumberUpperBound + // - upperBlockhash + // - blocknumberLowerBound + // - currentBlockTimestamp + struct Feed { + uint32 observationsTimestamp; // the timestamp of the most recent data assigned to this feed + int192 price; // the current price of the feed + int192 bid; // the current bid price of the feed + int192 ask; // the current ask price of the feed + string feedName; // the name of the feed + string feedId; // the id of the feed (hex encoded) + } + + // Report object obtained from off-chain Mercury server. + struct Report { + bytes32 feedId; // the feed Id of the report + uint32 observationsTimestamp; // the timestamp of when the data was observed + int192 price; // the median value of the OCR round + int192 bid; // the median bid of the OCR round + int192 ask; // the median ask if the OCR round + uint64 blocknumberUpperBound; // the highest block observed at the time the report was generated + bytes32 upperBlockhash; // the blockhash of the highest block observed + uint64 blocknumberLowerBound; // the lowest block observed at the time the report was generated + uint64 currentBlockTimestamp; // the timestamp of the highest block observed + } + + event FeedUpdated(uint32 observationsTimestamp, int192 price, int192 bid, int192 ask, string feedId); + + string constant c_feedParamKey = "feedIdHex"; // for Mercury v0.2 - format by which feeds are identified + string constant c_timeParamKey = "blockNumber"; // for Mercury v0.2 - format by which feeds are filtered to be sufficiently recent + IVerifierProxy immutable i_verifier; // for Mercury v0.2 - verifies off-chain reports + + int192 constant scale = 1_000_000; // a scalar used for measuring deviation with precision + int192 s_deviationPercentagePPM; // acceptable deviatoin threshold - 1.5% = 15_000, 100% = 1_000_000, etc.. + uint32 s_stalenessSeconds; // acceptable staleness threshold - 60 = 1 minute, 300 = 5 minutes, etc.. + + string[] public s_feeds; // list of feed Ids + mapping(string => Feed) public s_feedMapping; // mapping of feed Ids to stored feed data + + constructor( + string[] memory feedIds, + string[] memory feedNames, + address verifier, + int192 deviationPercentagePPM, + uint32 stalenessSeconds + ) { + i_verifier = IVerifierProxy(verifier); + + // Ensure correctly formatted constructor arguments. + require(feedIds.length == feedNames.length, "incorrect constructor args"); + + // Store desired deviation threshold and staleness seconds. + s_deviationPercentagePPM = deviationPercentagePPM; + s_stalenessSeconds = stalenessSeconds; + + // Store desired feeds. + s_feeds = feedIds; + for (uint256 i = 0; i < feedIds.length; i++) { + s_feedMapping[s_feeds[i]] = Feed({ + feedName: feedNames[i], + feedId: feedIds[i], + price: 0, + bid: 0, + ask: 0, + observationsTimestamp: 0 + }); } + } - // Report object obtained from off-chain Mercury server. - struct Report { - bytes32 feedId; // the feed Id of the report - uint32 observationsTimestamp; // the timestamp of when the data was observed - int192 price; // the median value of the OCR round - int192 bid; // the median bid of the OCR round - int192 ask; // the median ask if the OCR round - uint64 blocknumberUpperBound; // the highest block observed at the time the report was generated - bytes32 upperBlockhash; // the blockhash of the highest block observed - uint64 blocknumberLowerBound; // the lowest block observed at the time the report was generated - uint64 currentBlockTimestamp; // the timestamp of the highest block observed + // Returns a user-defined batch of feed data, based on the on-chain state. + function getLatestFeedData(string[] memory feedIds) external view returns (Feed[] memory) { + Feed[] memory feeds = new Feed[](feedIds.length); + for (uint256 i = 0; i < feedIds.length; i++) { + feeds[i] = s_feedMapping[feedIds[i]]; } - event FeedUpdated(uint32 observationsTimestamp, int192 price, int192 bid, int192 ask, string feedId); - - string constant c_feedParamKey = "feedIdHex"; // for Mercury v0.2 - format by which feeds are identified - string constant c_timeParamKey = "blockNumber"; // for Mercury v0.2 - format by which feeds are filtered to be sufficiently recent - IVerifierProxy immutable i_verifier; // for Mercury v0.2 - verifies off-chain reports - - int192 constant scale = 1_000_000; // a scalar used for measuring deviation with precision - int192 s_deviationPercentagePPM; // acceptable deviatoin threshold - 1.5% = 15_000, 100% = 1_000_000, etc.. - uint32 s_stalenessSeconds; // acceptable staleness threshold - 60 = 1 minute, 300 = 5 minutes, etc.. - - string[] public s_feeds; // list of feed Ids - mapping(string => Feed) public s_feedMapping; // mapping of feed Ids to stored feed data - - constructor( - string[] memory feedIds, - string[] memory feedNames, - address verifier, - int192 deviationPercentagePPM, - uint32 stalenessSeconds - ) { - i_verifier = IVerifierProxy(verifier); - - // Ensure correctly formatted constructor arguments. - require(feedIds.length == feedNames.length, "incorrect constructor args"); - - // Store desired deviation threshold and staleness seconds. - s_deviationPercentagePPM = deviationPercentagePPM; - s_stalenessSeconds = stalenessSeconds; - - // Store desired feeds. - s_feeds = feedIds; - for (uint256 i = 0; i < feedIds.length; i++) { - s_feedMapping[s_feeds[i]] = - Feed({feedName: feedNames[i], feedId: feedIds[i], price: 0, bid: 0, ask: 0, observationsTimestamp: 0}); - } + return feeds; + } + + // Invoke a feed lookup through the checkUpkeep function. Expected to run on a chron schedule. + function checkUpkeep(bytes calldata /* data */) external view override returns (bool, bytes memory) { + string[] memory feeds = s_feeds; + return revertForFeedLookup(feeds); + } + + // Extracted from `checkUpkeep` for batching purposes. + function revertForFeedLookup(string[] memory feeds) public view returns (bool, bytes memory) { + uint256 blockNumber = ChainSpecificUtil.getBlockNumber(); + revert FeedLookup(c_feedParamKey, feeds, c_timeParamKey, blockNumber, "EXTRA_DATA_FOR_FUTURE_FUNCTIONS_CALLS"); + } + + // Filter for feeds that have deviated sufficiently from their respective on-chain values, or where + // the on-chain values are sufficiently stale. + function checkCallback( + bytes[] memory values, + bytes memory lookupData + ) external view override returns (bool, bytes memory) { + bytes[] memory filteredValues = new bytes[](values.length); + uint256 count = 0; + for (uint256 i = 0; i < values.length; i++) { + Report memory report = getReport(values[i]); + string memory feedId = bytes32ToHextString(abi.encodePacked(report.feedId)); + Feed memory feed = s_feedMapping[feedId]; + if ( + (report.observationsTimestamp - feed.observationsTimestamp > s_stalenessSeconds) || + deviationExceedsThreshold(feed.price, report.price) + ) { + filteredValues[count] = values[i]; + count++; + } } - // Returns a user-defined batch of feed data, based on the on-chain state. - function getLatestFeedData(string[] memory feedIds) external view returns (Feed[] memory) { - Feed[] memory feeds = new Feed[](feedIds.length); - for (uint256 i = 0; i < feedIds.length; i++) { - feeds[i] = s_feedMapping[feedIds[i]]; - } - - return feeds; - } - - // Invoke a feed lookup through the checkUpkeep function. Expected to run on a chron schedule. - function checkUpkeep(bytes calldata /* data */ ) external view override returns (bool, bytes memory) { - string[] memory feeds = s_feeds; - return revertForFeedLookup(feeds); - } - - // Extracted from `checkUpkeep` for batching purposes. - function revertForFeedLookup(string[] memory feeds) public view returns (bool, bytes memory) { - uint256 blockNumber = ChainSpecificUtil.getBlockNumber(); - revert FeedLookup(c_feedParamKey, feeds, c_timeParamKey, blockNumber, "EXTRA_DATA_FOR_FUTURE_FUNCTIONS_CALLS"); + // Adjusts the lenght of the filteredValues array to `count` such that it + // does not have extra empty slots, in case some items were filtered. + assembly { + mstore(filteredValues, count) } - // Filter for feeds that have deviated sufficiently from their respective on-chain values, or where - // the on-chain values are sufficiently stale. - function checkCallback(bytes[] memory values, bytes memory lookupData) - external - view - override - returns (bool, bytes memory) - { - bytes[] memory filteredValues = new bytes[](values.length); - uint256 count = 0; - for (uint256 i = 0; i < values.length; i++) { - Report memory report = getReport(values[i]); - string memory feedId = bytes32ToHextString(abi.encodePacked(report.feedId)); - Feed memory feed = s_feedMapping[feedId]; - if ( - (report.observationsTimestamp - feed.observationsTimestamp > s_stalenessSeconds) - || deviationExceedsThreshold(feed.price, report.price) - ) { - filteredValues[count] = values[i]; - count++; - } - } - - // Adjusts the lenght of the filteredValues array to `count` such that it - // does not have extra empty slots, in case some items were filtered. - assembly { - mstore(filteredValues, count) - } - - bytes memory performData = abi.encode(filteredValues, lookupData); - return (filteredValues.length > 0, performData); + bytes memory performData = abi.encode(filteredValues, lookupData); + return (filteredValues.length > 0, performData); + } + + // Use deviated off-chain values to update on-chain state. + // TODO: + // - The implementation provided here is readable but crude. Remaining gas should be checked between iterations + // of the for-loop, and the failure of a single item should not cause the entire batch to revert. + function performUpkeep(bytes calldata performData) external override { + (bytes[] memory values /* bytes memory lookupData */, ) = abi.decode(performData, (bytes[], bytes)); + for (uint256 i = 0; i < values.length; i++) { + // Verify and decode report. + i_verifier.verify(values[i]); + Report memory report = getReport(values[i]); + string memory feedId = bytes32ToHextString(abi.encodePacked(report.feedId)); + + // Feeds that have been removed between checkUpkeep and performUpkeep should not be updated. + require(bytes(s_feedMapping[feedId].feedId).length > 0, "feed removed"); + + // Sanity check. Stale reports should not get through, but ensure they do not cause a regression + // in the registry. + require(s_feedMapping[feedId].observationsTimestamp <= report.observationsTimestamp, "stale report"); + + // Assign new values to state. + s_feedMapping[feedId].bid = report.bid; + s_feedMapping[feedId].ask = report.ask; + s_feedMapping[feedId].price = report.price; + s_feedMapping[feedId].observationsTimestamp = report.observationsTimestamp; + + // Emit log (not gas efficient to do this for each update). + emit FeedUpdated(report.observationsTimestamp, report.price, report.bid, report.ask, feedId); } - - // Use deviated off-chain values to update on-chain state. - // TODO: - // - The implementation provided here is readable but crude. Remaining gas should be checked between iterations - // of the for-loop, and the failure of a single item should not cause the entire batch to revert. - function performUpkeep(bytes calldata performData) external override { - (bytes[] memory values, /* bytes memory lookupData */ ) = abi.decode(performData, (bytes[], bytes)); - for (uint256 i = 0; i < values.length; i++) { - // Verify and decode report. - i_verifier.verify(values[i]); - Report memory report = getReport(values[i]); - string memory feedId = bytes32ToHextString(abi.encodePacked(report.feedId)); - - // Feeds that have been removed between checkUpkeep and performUpkeep should not be updated. - require(bytes(s_feedMapping[feedId].feedId).length > 0, "feed removed"); - - // Sanity check. Stale reports should not get through, but ensure they do not cause a regression - // in the registry. - require(s_feedMapping[feedId].observationsTimestamp <= report.observationsTimestamp, "stale report"); - - // Assign new values to state. - s_feedMapping[feedId].bid = report.bid; - s_feedMapping[feedId].ask = report.ask; - s_feedMapping[feedId].price = report.price; - s_feedMapping[feedId].observationsTimestamp = report.observationsTimestamp; - - // Emit log (not gas efficient to do this for each update). - emit FeedUpdated(report.observationsTimestamp, report.price, report.bid, report.ask, feedId); - } - } - - // Decodes a mercury respone into an on-chain object. Thanks @mikestone!! - function getReport(bytes memory signedReport) internal pure returns (Report memory) { - ( - /* bytes32[3] memory reportContext */ - , - bytes memory reportData, - /* bytes32[] memory rs */ - , - /* bytes32[] memory ss */ - , - /* bytes32 rawVs */ - ) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32)); - - Report memory report = abi.decode(reportData, (Report)); - return report; - } - - // Check if the off-chain value has deviated sufficiently from the on-chain value to justify an update. - // `scale` is used to ensure precision is not lost. - function deviationExceedsThreshold(int192 onChain, int192 offChain) public view returns (bool) { - // Compute absolute difference between the on-chain and off-chain values. - int192 scaledDifference = (onChain - offChain) * scale; - if (scaledDifference < 0) { - scaledDifference = -scaledDifference; - } - - // Compare to the allowed deviation from the on-chain value. - int192 deviationMax = ((onChain * scale) * s_deviationPercentagePPM) / scale; - return scaledDifference > deviationMax; + } + + // Decodes a mercury respone into an on-chain object. Thanks @mikestone!! + function getReport(bytes memory signedReport) internal pure returns (Report memory) { + /* + * bytes32[3] memory reportContext, + * bytes memory reportData, + * bytes32[] memory rs, + * bytes32[] memory ss, + * bytes32 rawVs + **/ + (, bytes memory reportData, , , ) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32)); + + Report memory report = abi.decode(reportData, (Report)); + return report; + } + + // Check if the off-chain value has deviated sufficiently from the on-chain value to justify an update. + // `scale` is used to ensure precision is not lost. + function deviationExceedsThreshold(int192 onChain, int192 offChain) public view returns (bool) { + // Compute absolute difference between the on-chain and off-chain values. + int192 scaledDifference = (onChain - offChain) * scale; + if (scaledDifference < 0) { + scaledDifference = -scaledDifference; } - // Helper function to reconcile a difference in formatting: - // - Automation passes feedId into their off-chain lookup function as a string. - // - Mercury stores feedId in their reports as a bytes32. - function bytes32ToHextString(bytes memory buffer) internal pure returns (string memory) { - bytes memory converted = new bytes(buffer.length * 2); - bytes memory _base = "0123456789abcdef"; - for (uint256 i = 0; i < buffer.length; i++) { - converted[i * 2] = _base[uint8(buffer[i]) / _base.length]; - converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length]; - } - return string(abi.encodePacked("0x", converted)); + // Compare to the allowed deviation from the on-chain value. + int192 deviationMax = ((onChain * scale) * s_deviationPercentagePPM) / scale; + return scaledDifference > deviationMax; + } + + // Helper function to reconcile a difference in formatting: + // - Automation passes feedId into their off-chain lookup function as a string. + // - Mercury stores feedId in their reports as a bytes32. + function bytes32ToHextString(bytes memory buffer) internal pure returns (string memory) { + bytes memory converted = new bytes(buffer.length * 2); + bytes memory _base = "0123456789abcdef"; + for (uint256 i = 0; i < buffer.length; i++) { + converted[i * 2] = _base[uint8(buffer[i]) / _base.length]; + converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length]; } + return string(abi.encodePacked("0x", converted)); + } } interface IVerifierProxy { - /** - * @notice Verifies that the data encoded has been signed - * correctly by routing to the correct verifier, and bills the user if applicable. - * @param payload The encoded data to be verified, including the signed - * report and any metadata for billing. - * @return verifiedReport The encoded report from the verifier. - */ - function verify(bytes calldata payload) external payable returns (bytes memory verifiedReport); + /** + * @notice Verifies that the data encoded has been signed + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payload The encoded data to be verified, including the signed + * report and any metadata for billing. + * @return verifiedReport The encoded report from the verifier. + */ + function verify(bytes calldata payload) external payable returns (bytes memory verifiedReport); } diff --git a/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistryBatchUpkeep.sol b/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistryBatchUpkeep.sol index a7811c89f49..31cbc149da2 100644 --- a/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistryBatchUpkeep.sol +++ b/contracts/src/v0.8/dev/automation/upkeeps/MercuryRegistryBatchUpkeep.sol @@ -5,50 +5,48 @@ import "../2_1/interfaces/FeedLookupCompatibleInterface.sol"; import "./MercuryRegistry.sol"; contract MercuryRegistryBatchUpkeep is AutomationCompatibleInterface, FeedLookupCompatibleInterface { - // Use a reasonable maximum batch size. Every Mercury report is ~750 bytes, too many reports - // passed into a single batch could exceed the calldata or transaction size limit for some blockchains. - uint256 constant MAX_BATCH_SIZE = 50; - - MercuryRegistry immutable i_registry; // master registry, where feed data is stored - - uint256 s_batchStart; // starting index of upkeep batch, inclusive - uint256 s_batchEnd; // ending index of upkeep batch, exclusive - - constructor(address mercuryRegistry, uint256 batchStart, uint256 batchEnd) { - i_registry = MercuryRegistry(mercuryRegistry); - - // Do not allow a batched mercury registry to use an excessive batch size, as to avoid - // calldata size limits. If more feeds need to be updated than allowed by the batch size, - // deploy another `MercuryRegistryBatchUpkeep` contract and register another upkeep job. - require(batchEnd - batchStart <= MAX_BATCH_SIZE, "batch size is too large"); - - s_batchStart = batchStart; - s_batchEnd = batchEnd; - } - - // Invoke a feed lookup for the feeds this upkeep is responsible for. - function checkUpkeep(bytes calldata /* data */ ) external view override returns (bool, bytes memory) { - uint256 start = s_batchStart; - uint256 end = s_batchEnd; - string[] memory feeds = new string[](end - start); - for (uint256 i = start; i < end; i++) { - feeds[i - start] = i_registry.s_feeds(i); - } - return i_registry.revertForFeedLookup(feeds); - } - - // Use the master registry to assess deviations. - function checkCallback(bytes[] memory values, bytes memory lookupData) - external - view - override - returns (bool, bytes memory) - { - return i_registry.checkCallback(values, lookupData); - } - - // Use the master registry to update state. - function performUpkeep(bytes calldata performData) external override { - i_registry.performUpkeep(performData); + // Use a reasonable maximum batch size. Every Mercury report is ~750 bytes, too many reports + // passed into a single batch could exceed the calldata or transaction size limit for some blockchains. + uint256 constant MAX_BATCH_SIZE = 50; + + MercuryRegistry immutable i_registry; // master registry, where feed data is stored + + uint256 s_batchStart; // starting index of upkeep batch, inclusive + uint256 s_batchEnd; // ending index of upkeep batch, exclusive + + constructor(address mercuryRegistry, uint256 batchStart, uint256 batchEnd) { + i_registry = MercuryRegistry(mercuryRegistry); + + // Do not allow a batched mercury registry to use an excessive batch size, as to avoid + // calldata size limits. If more feeds need to be updated than allowed by the batch size, + // deploy another `MercuryRegistryBatchUpkeep` contract and register another upkeep job. + require(batchEnd - batchStart <= MAX_BATCH_SIZE, "batch size is too large"); + + s_batchStart = batchStart; + s_batchEnd = batchEnd; + } + + // Invoke a feed lookup for the feeds this upkeep is responsible for. + function checkUpkeep(bytes calldata /* data */) external view override returns (bool, bytes memory) { + uint256 start = s_batchStart; + uint256 end = s_batchEnd; + string[] memory feeds = new string[](end - start); + for (uint256 i = start; i < end; i++) { + feeds[i - start] = i_registry.s_feeds(i); } + return i_registry.revertForFeedLookup(feeds); + } + + // Use the master registry to assess deviations. + function checkCallback( + bytes[] memory values, + bytes memory lookupData + ) external view override returns (bool, bytes memory) { + return i_registry.checkCallback(values, lookupData); + } + + // Use the master registry to update state. + function performUpkeep(bytes calldata performData) external override { + i_registry.performUpkeep(performData); + } } diff --git a/contracts/test/v0.8/foundry/automation/MercuryRegistry.t.sol b/contracts/test/v0.8/foundry/automation/MercuryRegistry.t.sol index f31017e6ef7..5d6d4a8ac0d 100644 --- a/contracts/test/v0.8/foundry/automation/MercuryRegistry.t.sol +++ b/contracts/test/v0.8/foundry/automation/MercuryRegistry.t.sol @@ -6,234 +6,232 @@ import "../../../../src/v0.8/dev/automation/upkeeps/MercuryRegistryBatchUpkeep.s import "../../../../src/v0.8/dev/automation/2_1/interfaces/FeedLookupCompatibleInterface.sol"; contract MercuryRegistryTest is Test { - address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; - address internal constant VERIFIER = 0x60448B880c9f3B501af3f343DA9284148BD7D77C; - int192 internal constant DEVIATION_THRESHOLD = 10_000; // 1% - uint32 internal constant STALENESS_SECONDS = 3600; // 1 hour - uint256 internal constant BLOCK_NUMBER = 36209282; - - string[] feedIds; - string s_BTCUSDFeedId = "0x6962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb2"; - string s_ETHUSDFeedId = "0xf753e1201d54ac94dfd9334c542562ff7e42993419a661261d010af0cbfd4e34"; - MercuryRegistry s_testRegistry; - - // Feed: BTC/USD - // Date: Tuesday, August 22, 2023 7:29:28 PM - // Price: $25,857.11126720 - bytes s_august22BTCUSDMercuryReport = - hex"0006a2f7f9b6c10385739c687064aa1e457812927f59446cccddf7740cc025ad00000000000000000000000000000000000000000000000000000000014cb94e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb20000000000000000000000000000000000000000000000000000000064e50c980000000000000000000000000000000000000000000000000000025a0864a8c00000000000000000000000000000000000000000000000000000025a063481720000000000000000000000000000000000000000000000000000025a0a94d00f000000000000000000000000000000000000000000000000000000000226181f4733a6d98892d1821771c041d5d69298210fdca9d643ad74477423b6a3045647000000000000000000000000000000000000000000000000000000000226181f0000000000000000000000000000000000000000000000000000000064e50c9700000000000000000000000000000000000000000000000000000000000000027f3056b1b71dd516037afd2e636f8afb39853f5cb3ccaa4b02d6f9a2a64622534e94aa1f794f6a72478deb7e0eb2942864b7fac76d6e120bd809530b1b74a32b00000000000000000000000000000000000000000000000000000000000000027bd3b385c0812dfcad2652d225410a014a0b836cd9635a6e7fb404f65f7a912f0b193db57e5c4f38ce71f29170f7eadfa94d972338858bacd59ab224245206db"; - - // Feed: BTC/USD - // Date: Wednesday, August 23, 2023 7:55:02 PM - // Price: $26,720.37346975 - bytes s_august23BTCUSDMercuryReport = - hex"0006a2f7f9b6c10385739c687064aa1e457812927f59446cccddf7740cc025ad000000000000000000000000000000000000000000000000000000000159a630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb20000000000000000000000000000000000000000000000000000000064e664160000000000000000000000000000000000000000000000000000026e21d63e9f0000000000000000000000000000000000000000000000000000026e2147576a0000000000000000000000000000000000000000000000000000026e226525d30000000000000000000000000000000000000000000000000000000002286ce7c44fa27f67f6dd0a8bb40c12f0f050231845789f022a82aa5f4b3fe5bf2068fb0000000000000000000000000000000000000000000000000000000002286ce70000000000000000000000000000000000000000000000000000000064e664150000000000000000000000000000000000000000000000000000000000000002e9c5857631172082a47a20aa2fd9f580c1c48275d030c17a2dff77da04f88708ce776ef74c04b9ef6ba87c56d8f8c57e80ddd5298b477d60dd49fb8120f1b9ce000000000000000000000000000000000000000000000000000000000000000248624e0e2341cdaf989098f8b3dee2660b792b24e5251d6e48e3abe0a879c0683163a3a199969010e15353a99926d113f6d4cbab9d82ae90a159af9f74f8c157"; - - // Feed: BTC/USD - // Date: Wednesday, August 23, 2023 8:13:28 PM - // Price: $26,559.67100000 - bytes s_august23BTCUSDMercuryReport_2 = - hex"0006a2f7f9b6c10385739c687064aa1e457812927f59446cccddf7740cc025ad000000000000000000000000000000000000000000000000000000000159d009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb20000000000000000000000000000000000000000000000000000000064e668690000000000000000000000000000000000000000000000000000026a63f9bc600000000000000000000000000000000000000000000000000000026a635984c00000000000000000000000000000000000000000000000000000026a67bb929d00000000000000000000000000000000000000000000000000000000022873e999d3ff9b644bba530af933dfaa6c59e31c3e232fcaa1e5f7304e2e79d939da1900000000000000000000000000000000000000000000000000000000022873e80000000000000000000000000000000000000000000000000000000064e66868000000000000000000000000000000000000000000000000000000000000000247c21657a6c2795986e95081876bf8b5f24bf72abd2dc4c601e7c96d654bcf543b5bb730e3d4736a308095e4531e7c03f581ac364f0889922ba3ae24b7cf968000000000000000000000000000000000000000000000000000000000000000020d3037d9f55256a001a2aa79ea746526c7cb36747e1deb4c804311394b4027667e5b711bcecfe60632e86cf8e83c28d1465e2d8d90bc0638dad8347f55488e8e"; - - // Feed: ETH/USD - // Date: Wednesday, August 23, 2023 7:55:01 PM - // Price: $1,690.76482169 - bytes s_august23ETHUSDMercuryReport = - hex"0006c41ec94138ae62cce3f1a2b852e42fe70359502fa7b6bdbf81207970d88e00000000000000000000000000000000000000000000000000000000016d874d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120f753e1201d54ac94dfd9334c542562ff7e42993419a661261d010af0cbfd4e340000000000000000000000000000000000000000000000000000000064e66415000000000000000000000000000000000000000000000000000000275dbe6079000000000000000000000000000000000000000000000000000000275c905eba000000000000000000000000000000000000000000000000000000275e5693080000000000000000000000000000000000000000000000000000000002286ce7c44fa27f67f6dd0a8bb40c12f0f050231845789f022a82aa5f4b3fe5bf2068fb0000000000000000000000000000000000000000000000000000000002286ce70000000000000000000000000000000000000000000000000000000064e664150000000000000000000000000000000000000000000000000000000000000002a2b01f7741563cfe305efaec43e56cd85731e3a8e2396f7c625bd16adca7b39c97805b6170adc84d065f9d68c87104c3509aeefef42c0d1711e028ace633888000000000000000000000000000000000000000000000000000000000000000025d984ad476bda9547cf0f90d32732dc5a0d84b0e2fe9795149b786fb05332d4c092e278b4dddeef45c070b818c6e221db2633b573d616ef923c755a145ea099c"; - - function setUp() public virtual { - // Set owner, and fork Arbitrum Goerli Testnet (chain ID 421613). - // A public Arbitrum Goerli RPC url is being used, and the fork should be cached in CI so availability is not an issue for test runs. - vm.startPrank(OWNER); - vm.selectFork(vm.createFork("https://arbitrum-goerli.publicnode.com")); - vm.rollFork(BLOCK_NUMBER); - - // Create Mercury Registry. - feedIds = new string[](2); - feedIds[0] = s_BTCUSDFeedId; - feedIds[1] = s_ETHUSDFeedId; - string[] memory feedNames = new string[](2); - feedNames[0] = "BTC/USD"; - feedNames[1] = "ETH/USD"; - s_testRegistry = new MercuryRegistry(feedIds, feedNames, VERIFIER, DEVIATION_THRESHOLD, STALENESS_SECONDS); - } - - function testMercuryRegistry() public { - // Check upkeep, receive Mercury revert. - uint256 blockNumber = BLOCK_NUMBER; - vm.roll(blockNumber); - vm.expectRevert( - abi.encodeWithSelector( - FeedLookupCompatibleInterface.FeedLookup.selector, - "feedIdHex", // feedParamKey - feedIds, // feed Ids - "blockNumber", // timeParamKey - blockNumber, // block number on which request is occuring - "EXTRA_DATA_FOR_FUTURE_FUNCTIONS_CALLS" // extra data - ) - ); - s_testRegistry.checkUpkeep(""); - - // Obtain mercury report off-chain (for August 22 BTC/USD price) - bytes[] memory values = new bytes[](1); - values[0] = s_august22BTCUSDMercuryReport; - - // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. - (bool shouldPerformUpkeep, bytes memory performData) = - s_testRegistry.checkCallback(values, bytes("LOOKUP_DATA")); - assertEq(shouldPerformUpkeep, true); - - // Perform upkeep to update on-chain state. - s_testRegistry.performUpkeep(performData); - - // Check state of BTC/USD feed to ensure update was propagated. - ( - uint32 observationsTimestamp, - int192 price, - int192 bid, - int192 ask, - string memory feedName, - string memory localFeedId - ) = s_testRegistry.s_feedMapping(s_BTCUSDFeedId); - assertEq(observationsTimestamp, 1692732568); // Tuesday, August 22, 2023 7:29:28 PM - assertEq(bid, 2585674416498); // $25,856.74416498 - assertEq(price, 2585711126720); // $25,857.11126720 - assertEq(ask, 2585747836943); // $25,857.47836943 - assertEq(feedName, "BTC/USD"); - assertEq(localFeedId, s_BTCUSDFeedId); - - // Obtain mercury report off-chain (for August 23 BTC/USD price & ETH/USD price) - values = new bytes[](2); - values[0] = s_august23BTCUSDMercuryReport; - values[1] = s_august23ETHUSDMercuryReport; - - // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. - (shouldPerformUpkeep, performData) = s_testRegistry.checkCallback(values, bytes("LOOKUP_DATA")); - assertEq(shouldPerformUpkeep, true); - - // Perform upkeep to update on-chain state. - s_testRegistry.performUpkeep(performData); - - // Make a batch request for both the BTC/USD feed data and the ETH/USD feed data. - MercuryRegistry.Feed[] memory feeds = s_testRegistry.getLatestFeedData(feedIds); - - // Check state of BTC/USD feed to ensure update was propagated. - assertEq(feeds[0].observationsTimestamp, 1692820502); // Wednesday, August 23, 2023 7:55:02 PM - assertEq(feeds[0].bid, 2672027981674); // $26,720.27981674 - assertEq(feeds[0].price, 2672037346975); // $26,720.37346975 - assertEq(feeds[0].ask, 2672046712275); // $26,720.46712275 - assertEq(feeds[0].feedName, "BTC/USD"); - assertEq(feeds[0].feedId, s_BTCUSDFeedId); - - // Check state of ETH/USD feed to ensure update was propagated. - assertEq(feeds[1].observationsTimestamp, 1692820501); // Wednesday, August 23, 2023 7:55:01 PM - assertEq(feeds[1].bid, 169056689850); // $1,690.56689850 - assertEq(feeds[1].price, 169076482169); // $1,690.76482169 - assertEq(feeds[1].ask, 169086456584); // $16,90.86456584 - assertEq(feeds[1].feedName, "ETH/USD"); - assertEq(feeds[1].feedId, s_ETHUSDFeedId); - - // Obtain mercury report off-chain for August 23 BTC/USD price (second report of the day). - // The price of this incoming report will not deviate enough from the on-chain value to trigger an update, - // nor is the on-chain data stale enough. - values = new bytes[](1); - values[0] = s_august23BTCUSDMercuryReport_2; - - // Pass the obtained mercury report into checkCallback, to assert that an update is not warranted. - (shouldPerformUpkeep, performData) = s_testRegistry.checkCallback(values, bytes("LOOKUP_DATA")); - assertEq(shouldPerformUpkeep, false); - } - - // Below are the same tests as `testMercuryRegistry`, except done via a batching Mercury registry that - // consumes the test registry. This is to assert that batching can be accomplished by multiple different - // upkeep jobs, which can populate the same - function testMercuryRegistryBatchUpkeep() public { - MercuryRegistryBatchUpkeep batchedRegistry = new MercuryRegistryBatchUpkeep( - address(s_testRegistry), // use the test registry as master registry - 0, // start batch at index 0. - feedIds.length // end batch at end of feedIds (take responsibility for all feeds) - ); - // Check upkeep, receive Mercury revert. - uint256 blockNumber = BLOCK_NUMBER; - vm.roll(blockNumber); - vm.expectRevert( - abi.encodeWithSelector( - FeedLookupCompatibleInterface.FeedLookup.selector, - "feedIdHex", // feedParamKey - feedIds, // feed Ids - "blockNumber", // timeParamKey - blockNumber, // block number on which request is occuring - "EXTRA_DATA_FOR_FUTURE_FUNCTIONS_CALLS" // extra data - ) - ); - batchedRegistry.checkUpkeep(""); - - // Obtain mercury report off-chain (for August 22 BTC/USD price) - bytes[] memory values = new bytes[](1); - values[0] = s_august22BTCUSDMercuryReport; - - // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. - (bool shouldPerformUpkeep, bytes memory performData) = - batchedRegistry.checkCallback(values, bytes("LOOKUP_DATA")); - assertEq(shouldPerformUpkeep, true); - - // Perform upkeep to update on-chain state. - batchedRegistry.performUpkeep(performData); - - // Check state of BTC/USD feed to ensure update was propagated. - ( - uint32 observationsTimestamp, - int192 price, - int192 bid, - int192 ask, - string memory feedName, - string memory localFeedId - ) = s_testRegistry.s_feedMapping(s_BTCUSDFeedId); - assertEq(observationsTimestamp, 1692732568); // Tuesday, August 22, 2023 7:29:28 PM - assertEq(bid, 2585674416498); // $25,856.74416498 - assertEq(price, 2585711126720); // $25,857.11126720 - assertEq(ask, 2585747836943); // $25,857.47836943 - assertEq(feedName, "BTC/USD"); - assertEq(localFeedId, s_BTCUSDFeedId); - - // Obtain mercury report off-chain (for August 23 BTC/USD price & ETH/USD price) - values = new bytes[](2); - values[0] = s_august23BTCUSDMercuryReport; - values[1] = s_august23ETHUSDMercuryReport; - - // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. - (shouldPerformUpkeep, performData) = batchedRegistry.checkCallback(values, bytes("LOOKUP_DATA")); - assertEq(shouldPerformUpkeep, true); - - // Perform upkeep to update on-chain state. - batchedRegistry.performUpkeep(performData); - - // Make a batch request for both the BTC/USD feed data and the ETH/USD feed data. - MercuryRegistry.Feed[] memory feeds = s_testRegistry.getLatestFeedData(feedIds); - - // Check state of BTC/USD feed to ensure update was propagated. - assertEq(feeds[0].observationsTimestamp, 1692820502); // Wednesday, August 23, 2023 7:55:02 PM - assertEq(feeds[0].bid, 2672027981674); // $26,720.27981674 - assertEq(feeds[0].price, 2672037346975); // $26,720.37346975 - assertEq(feeds[0].ask, 2672046712275); // $26,720.46712275 - assertEq(feeds[0].feedName, "BTC/USD"); - assertEq(feeds[0].feedId, s_BTCUSDFeedId); - - // Check state of ETH/USD feed to ensure update was propagated. - assertEq(feeds[1].observationsTimestamp, 1692820501); // Wednesday, August 23, 2023 7:55:01 PM - assertEq(feeds[1].bid, 169056689850); // $1,690.56689850 - assertEq(feeds[1].price, 169076482169); // $1,690.76482169 - assertEq(feeds[1].ask, 169086456584); // $16,90.86456584 - assertEq(feeds[1].feedName, "ETH/USD"); - assertEq(feeds[1].feedId, s_ETHUSDFeedId); - - // Obtain mercury report off-chain for August 23 BTC/USD price (second report of the day). - // The price of this incoming report will not deviate enough from the on-chain value to trigger an update. - values = new bytes[](1); - values[0] = s_august23BTCUSDMercuryReport_2; - - // Pass the obtained mercury report into checkCallback, to assert that an update is not warranted. - (shouldPerformUpkeep, performData) = batchedRegistry.checkCallback(values, bytes("LOOKUP_DATA")); - assertEq(shouldPerformUpkeep, false); - } + address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; + address internal constant VERIFIER = 0x60448B880c9f3B501af3f343DA9284148BD7D77C; + int192 internal constant DEVIATION_THRESHOLD = 10_000; // 1% + uint32 internal constant STALENESS_SECONDS = 3600; // 1 hour + uint256 internal constant BLOCK_NUMBER = 36209282; + + string[] feedIds; + string s_BTCUSDFeedId = "0x6962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb2"; + string s_ETHUSDFeedId = "0xf753e1201d54ac94dfd9334c542562ff7e42993419a661261d010af0cbfd4e34"; + MercuryRegistry s_testRegistry; + + // Feed: BTC/USD + // Date: Tuesday, August 22, 2023 7:29:28 PM + // Price: $25,857.11126720 + bytes s_august22BTCUSDMercuryReport = + hex"0006a2f7f9b6c10385739c687064aa1e457812927f59446cccddf7740cc025ad00000000000000000000000000000000000000000000000000000000014cb94e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb20000000000000000000000000000000000000000000000000000000064e50c980000000000000000000000000000000000000000000000000000025a0864a8c00000000000000000000000000000000000000000000000000000025a063481720000000000000000000000000000000000000000000000000000025a0a94d00f000000000000000000000000000000000000000000000000000000000226181f4733a6d98892d1821771c041d5d69298210fdca9d643ad74477423b6a3045647000000000000000000000000000000000000000000000000000000000226181f0000000000000000000000000000000000000000000000000000000064e50c9700000000000000000000000000000000000000000000000000000000000000027f3056b1b71dd516037afd2e636f8afb39853f5cb3ccaa4b02d6f9a2a64622534e94aa1f794f6a72478deb7e0eb2942864b7fac76d6e120bd809530b1b74a32b00000000000000000000000000000000000000000000000000000000000000027bd3b385c0812dfcad2652d225410a014a0b836cd9635a6e7fb404f65f7a912f0b193db57e5c4f38ce71f29170f7eadfa94d972338858bacd59ab224245206db"; + + // Feed: BTC/USD + // Date: Wednesday, August 23, 2023 7:55:02 PM + // Price: $26,720.37346975 + bytes s_august23BTCUSDMercuryReport = + hex"0006a2f7f9b6c10385739c687064aa1e457812927f59446cccddf7740cc025ad000000000000000000000000000000000000000000000000000000000159a630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb20000000000000000000000000000000000000000000000000000000064e664160000000000000000000000000000000000000000000000000000026e21d63e9f0000000000000000000000000000000000000000000000000000026e2147576a0000000000000000000000000000000000000000000000000000026e226525d30000000000000000000000000000000000000000000000000000000002286ce7c44fa27f67f6dd0a8bb40c12f0f050231845789f022a82aa5f4b3fe5bf2068fb0000000000000000000000000000000000000000000000000000000002286ce70000000000000000000000000000000000000000000000000000000064e664150000000000000000000000000000000000000000000000000000000000000002e9c5857631172082a47a20aa2fd9f580c1c48275d030c17a2dff77da04f88708ce776ef74c04b9ef6ba87c56d8f8c57e80ddd5298b477d60dd49fb8120f1b9ce000000000000000000000000000000000000000000000000000000000000000248624e0e2341cdaf989098f8b3dee2660b792b24e5251d6e48e3abe0a879c0683163a3a199969010e15353a99926d113f6d4cbab9d82ae90a159af9f74f8c157"; + + // Feed: BTC/USD + // Date: Wednesday, August 23, 2023 8:13:28 PM + // Price: $26,559.67100000 + bytes s_august23BTCUSDMercuryReport_2 = + hex"0006a2f7f9b6c10385739c687064aa1e457812927f59446cccddf7740cc025ad000000000000000000000000000000000000000000000000000000000159d009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000280010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001206962e629c3a0f5b7e3e9294b0c283c9b20f94f1c89c8ba8c1ee4650738f20fb20000000000000000000000000000000000000000000000000000000064e668690000000000000000000000000000000000000000000000000000026a63f9bc600000000000000000000000000000000000000000000000000000026a635984c00000000000000000000000000000000000000000000000000000026a67bb929d00000000000000000000000000000000000000000000000000000000022873e999d3ff9b644bba530af933dfaa6c59e31c3e232fcaa1e5f7304e2e79d939da1900000000000000000000000000000000000000000000000000000000022873e80000000000000000000000000000000000000000000000000000000064e66868000000000000000000000000000000000000000000000000000000000000000247c21657a6c2795986e95081876bf8b5f24bf72abd2dc4c601e7c96d654bcf543b5bb730e3d4736a308095e4531e7c03f581ac364f0889922ba3ae24b7cf968000000000000000000000000000000000000000000000000000000000000000020d3037d9f55256a001a2aa79ea746526c7cb36747e1deb4c804311394b4027667e5b711bcecfe60632e86cf8e83c28d1465e2d8d90bc0638dad8347f55488e8e"; + + // Feed: ETH/USD + // Date: Wednesday, August 23, 2023 7:55:01 PM + // Price: $1,690.76482169 + bytes s_august23ETHUSDMercuryReport = + hex"0006c41ec94138ae62cce3f1a2b852e42fe70359502fa7b6bdbf81207970d88e00000000000000000000000000000000000000000000000000000000016d874d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120f753e1201d54ac94dfd9334c542562ff7e42993419a661261d010af0cbfd4e340000000000000000000000000000000000000000000000000000000064e66415000000000000000000000000000000000000000000000000000000275dbe6079000000000000000000000000000000000000000000000000000000275c905eba000000000000000000000000000000000000000000000000000000275e5693080000000000000000000000000000000000000000000000000000000002286ce7c44fa27f67f6dd0a8bb40c12f0f050231845789f022a82aa5f4b3fe5bf2068fb0000000000000000000000000000000000000000000000000000000002286ce70000000000000000000000000000000000000000000000000000000064e664150000000000000000000000000000000000000000000000000000000000000002a2b01f7741563cfe305efaec43e56cd85731e3a8e2396f7c625bd16adca7b39c97805b6170adc84d065f9d68c87104c3509aeefef42c0d1711e028ace633888000000000000000000000000000000000000000000000000000000000000000025d984ad476bda9547cf0f90d32732dc5a0d84b0e2fe9795149b786fb05332d4c092e278b4dddeef45c070b818c6e221db2633b573d616ef923c755a145ea099c"; + + function setUp() public virtual { + // Set owner, and fork Arbitrum Goerli Testnet (chain ID 421613). + // A public Arbitrum Goerli RPC url is being used, and the fork should be cached in CI so availability is not an issue for test runs. + vm.startPrank(OWNER); + vm.selectFork(vm.createFork("https://arbitrum-goerli.publicnode.com")); + vm.rollFork(BLOCK_NUMBER); + + // Create Mercury Registry. + feedIds = new string[](2); + feedIds[0] = s_BTCUSDFeedId; + feedIds[1] = s_ETHUSDFeedId; + string[] memory feedNames = new string[](2); + feedNames[0] = "BTC/USD"; + feedNames[1] = "ETH/USD"; + s_testRegistry = new MercuryRegistry(feedIds, feedNames, VERIFIER, DEVIATION_THRESHOLD, STALENESS_SECONDS); + } + + function testMercuryRegistry() public { + // Check upkeep, receive Mercury revert. + uint256 blockNumber = BLOCK_NUMBER; + vm.roll(blockNumber); + vm.expectRevert( + abi.encodeWithSelector( + FeedLookupCompatibleInterface.FeedLookup.selector, + "feedIdHex", // feedParamKey + feedIds, // feed Ids + "blockNumber", // timeParamKey + blockNumber, // block number on which request is occuring + "EXTRA_DATA_FOR_FUTURE_FUNCTIONS_CALLS" // extra data + ) + ); + s_testRegistry.checkUpkeep(""); + + // Obtain mercury report off-chain (for August 22 BTC/USD price) + bytes[] memory values = new bytes[](1); + values[0] = s_august22BTCUSDMercuryReport; + + // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. + (bool shouldPerformUpkeep, bytes memory performData) = s_testRegistry.checkCallback(values, bytes("LOOKUP_DATA")); + assertEq(shouldPerformUpkeep, true); + + // Perform upkeep to update on-chain state. + s_testRegistry.performUpkeep(performData); + + // Check state of BTC/USD feed to ensure update was propagated. + ( + uint32 observationsTimestamp, + int192 price, + int192 bid, + int192 ask, + string memory feedName, + string memory localFeedId + ) = s_testRegistry.s_feedMapping(s_BTCUSDFeedId); + assertEq(observationsTimestamp, 1692732568); // Tuesday, August 22, 2023 7:29:28 PM + assertEq(bid, 2585674416498); // $25,856.74416498 + assertEq(price, 2585711126720); // $25,857.11126720 + assertEq(ask, 2585747836943); // $25,857.47836943 + assertEq(feedName, "BTC/USD"); + assertEq(localFeedId, s_BTCUSDFeedId); + + // Obtain mercury report off-chain (for August 23 BTC/USD price & ETH/USD price) + values = new bytes[](2); + values[0] = s_august23BTCUSDMercuryReport; + values[1] = s_august23ETHUSDMercuryReport; + + // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. + (shouldPerformUpkeep, performData) = s_testRegistry.checkCallback(values, bytes("LOOKUP_DATA")); + assertEq(shouldPerformUpkeep, true); + + // Perform upkeep to update on-chain state. + s_testRegistry.performUpkeep(performData); + + // Make a batch request for both the BTC/USD feed data and the ETH/USD feed data. + MercuryRegistry.Feed[] memory feeds = s_testRegistry.getLatestFeedData(feedIds); + + // Check state of BTC/USD feed to ensure update was propagated. + assertEq(feeds[0].observationsTimestamp, 1692820502); // Wednesday, August 23, 2023 7:55:02 PM + assertEq(feeds[0].bid, 2672027981674); // $26,720.27981674 + assertEq(feeds[0].price, 2672037346975); // $26,720.37346975 + assertEq(feeds[0].ask, 2672046712275); // $26,720.46712275 + assertEq(feeds[0].feedName, "BTC/USD"); + assertEq(feeds[0].feedId, s_BTCUSDFeedId); + + // Check state of ETH/USD feed to ensure update was propagated. + assertEq(feeds[1].observationsTimestamp, 1692820501); // Wednesday, August 23, 2023 7:55:01 PM + assertEq(feeds[1].bid, 169056689850); // $1,690.56689850 + assertEq(feeds[1].price, 169076482169); // $1,690.76482169 + assertEq(feeds[1].ask, 169086456584); // $16,90.86456584 + assertEq(feeds[1].feedName, "ETH/USD"); + assertEq(feeds[1].feedId, s_ETHUSDFeedId); + + // Obtain mercury report off-chain for August 23 BTC/USD price (second report of the day). + // The price of this incoming report will not deviate enough from the on-chain value to trigger an update, + // nor is the on-chain data stale enough. + values = new bytes[](1); + values[0] = s_august23BTCUSDMercuryReport_2; + + // Pass the obtained mercury report into checkCallback, to assert that an update is not warranted. + (shouldPerformUpkeep, performData) = s_testRegistry.checkCallback(values, bytes("LOOKUP_DATA")); + assertEq(shouldPerformUpkeep, false); + } + + // Below are the same tests as `testMercuryRegistry`, except done via a batching Mercury registry that + // consumes the test registry. This is to assert that batching can be accomplished by multiple different + // upkeep jobs, which can populate the same + function testMercuryRegistryBatchUpkeep() public { + MercuryRegistryBatchUpkeep batchedRegistry = new MercuryRegistryBatchUpkeep( + address(s_testRegistry), // use the test registry as master registry + 0, // start batch at index 0. + feedIds.length // end batch at end of feedIds (take responsibility for all feeds) + ); + // Check upkeep, receive Mercury revert. + uint256 blockNumber = BLOCK_NUMBER; + vm.roll(blockNumber); + vm.expectRevert( + abi.encodeWithSelector( + FeedLookupCompatibleInterface.FeedLookup.selector, + "feedIdHex", // feedParamKey + feedIds, // feed Ids + "blockNumber", // timeParamKey + blockNumber, // block number on which request is occuring + "EXTRA_DATA_FOR_FUTURE_FUNCTIONS_CALLS" // extra data + ) + ); + batchedRegistry.checkUpkeep(""); + + // Obtain mercury report off-chain (for August 22 BTC/USD price) + bytes[] memory values = new bytes[](1); + values[0] = s_august22BTCUSDMercuryReport; + + // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. + (bool shouldPerformUpkeep, bytes memory performData) = batchedRegistry.checkCallback(values, bytes("LOOKUP_DATA")); + assertEq(shouldPerformUpkeep, true); + + // Perform upkeep to update on-chain state. + batchedRegistry.performUpkeep(performData); + + // Check state of BTC/USD feed to ensure update was propagated. + ( + uint32 observationsTimestamp, + int192 price, + int192 bid, + int192 ask, + string memory feedName, + string memory localFeedId + ) = s_testRegistry.s_feedMapping(s_BTCUSDFeedId); + assertEq(observationsTimestamp, 1692732568); // Tuesday, August 22, 2023 7:29:28 PM + assertEq(bid, 2585674416498); // $25,856.74416498 + assertEq(price, 2585711126720); // $25,857.11126720 + assertEq(ask, 2585747836943); // $25,857.47836943 + assertEq(feedName, "BTC/USD"); + assertEq(localFeedId, s_BTCUSDFeedId); + + // Obtain mercury report off-chain (for August 23 BTC/USD price & ETH/USD price) + values = new bytes[](2); + values[0] = s_august23BTCUSDMercuryReport; + values[1] = s_august23ETHUSDMercuryReport; + + // Pass the obtained mercury report into checkCallback, to assert that an update is warranted. + (shouldPerformUpkeep, performData) = batchedRegistry.checkCallback(values, bytes("LOOKUP_DATA")); + assertEq(shouldPerformUpkeep, true); + + // Perform upkeep to update on-chain state. + batchedRegistry.performUpkeep(performData); + + // Make a batch request for both the BTC/USD feed data and the ETH/USD feed data. + MercuryRegistry.Feed[] memory feeds = s_testRegistry.getLatestFeedData(feedIds); + + // Check state of BTC/USD feed to ensure update was propagated. + assertEq(feeds[0].observationsTimestamp, 1692820502); // Wednesday, August 23, 2023 7:55:02 PM + assertEq(feeds[0].bid, 2672027981674); // $26,720.27981674 + assertEq(feeds[0].price, 2672037346975); // $26,720.37346975 + assertEq(feeds[0].ask, 2672046712275); // $26,720.46712275 + assertEq(feeds[0].feedName, "BTC/USD"); + assertEq(feeds[0].feedId, s_BTCUSDFeedId); + + // Check state of ETH/USD feed to ensure update was propagated. + assertEq(feeds[1].observationsTimestamp, 1692820501); // Wednesday, August 23, 2023 7:55:01 PM + assertEq(feeds[1].bid, 169056689850); // $1,690.56689850 + assertEq(feeds[1].price, 169076482169); // $1,690.76482169 + assertEq(feeds[1].ask, 169086456584); // $16,90.86456584 + assertEq(feeds[1].feedName, "ETH/USD"); + assertEq(feeds[1].feedId, s_ETHUSDFeedId); + + // Obtain mercury report off-chain for August 23 BTC/USD price (second report of the day). + // The price of this incoming report will not deviate enough from the on-chain value to trigger an update. + values = new bytes[](1); + values[0] = s_august23BTCUSDMercuryReport_2; + + // Pass the obtained mercury report into checkCallback, to assert that an update is not warranted. + (shouldPerformUpkeep, performData) = batchedRegistry.checkCallback(values, bytes("LOOKUP_DATA")); + assertEq(shouldPerformUpkeep, false); + } }