From 0a417b4ba81438d4ad12102dd92ed7aaa969bd81 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:50:45 +0100 Subject: [PATCH 01/14] Add ErrorHandler to Data Streams --- src/config/sidebar.ts | 9 + .../guides/streams-lookup-error-handler.mdx | 159 +----------------- .../streams-lookup-error-handler.mdx | 27 +++ .../data-streams/common/DataStreams.astro | 5 +- .../common/streamsLookupErrorHandler.mdx | 157 +++++++++++++++++ 5 files changed, 200 insertions(+), 157 deletions(-) create mode 100644 src/content/data-streams/tutorials/streams-lookup-error-handler.mdx create mode 100644 src/features/data-streams/common/streamsLookupErrorHandler.mdx diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 192e448439f..b9baff188d0 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -213,6 +213,15 @@ export const SIDEBAR: Partial> = { }, ], }, + { + section: "Guides", + contents: [ + { + title: "Using the StreamsLookup error handler", + url: "data-streams/tutorials/streams-lookup-error-handler", + }, + ], + }, { section: "Reference", contents: [ diff --git a/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx b/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx index 87a337a3c1f..5a74e9de694 100644 --- a/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx +++ b/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx @@ -8,7 +8,8 @@ whatsnext: { "Troubleshoot and Debug Upkeeps": "/chainlink-automation/reference/ "Automation Contracts": "/chainlink-automation/reference/automation-contracts" } --- -import { Aside, ClickToZoom, CodeSample } from "@components" +import { Aside } from "@components" +import DataStreams from "@features/data-streams/common/DataStreams.astro" -The Chainlink Automation StreamsLookup error handler provides insight into potential errors or edge cases in StreamsLookup upkeeps. The table below outlines a range of error codes and the behavior associated with the codes. Use the `checkErrorHandler` function to specify how you want to respond to the error codes. `checkErrorHandler` is simulated offchain and determines what action for Automation to take onchain in `performUpkeep`. - - - -## Error handler - -When Automation detects an event, it runs the `checkLog` function, which includes a [StreamsLookup revert](/chainlink-automation/reference/automation-interfaces#streamslookup-revert) custom error. The StreamsLookup revert enables your upkeep to fetch a report from Data Streams. If reports are fetched successfully, the [`checkCallback`](/chainlink-automation/reference/automation-interfaces#checkcallback-function) function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. Both of these functions have the same output types (`bool upkeepNeeded, bytes memory performData`), which Automation uses to run `performUpkeep` onchain. The [example code](#example-code) also shows each function outlined in the diagram below: - - - -If the Automation network fails to get the requested reports, an error code is sent to the `checkErrorHandler` function in your contract. If your contract doesn't have the `checkErrorHandler` function, nothing will happen. If your contract has the `checkErrorHandler` function, it is evaluated offchain to determine what to do next. For example, you could intercept or ignore certain errors and decide not to run `performUpkeep` in those cases, in order to save time and gas. For other errors, you can execute an alternative path within `performUpkeep`, and the upkeep runs the custom logic you define in your `performUpkeep` function to handle those errors. - -1. Add the `checkErrorHandler` function in your contract to specify how you want to handle [error codes](#error-codes). For example, you could decide to ignore any codes related to bad requests or incorrect input, without running `performUpkeep` onchain: - - ```solidity - function checkErrorHandler( - uint errorCode, - bytes calldata extraData - ) external returns (bool upkeepNeeded, bytes memory performData) { - // Add custom logic to handle errors offchain here - bool _upkeepNeeded = true; - bool success = false; - bool isError = true; - if (errorCode == 800400) { - // Handle bad request error code offchain - _upkeepNeeded = false; - } else { - // logic to handle other errors - } - return (_upkeepNeeded, abi.encode(isError, abi.encode(errorCode, extraData, success))); - } - ``` - -1. Define custom logic for the alternative path within `performUpkeep`, to handle any error codes you did not intercept offchain in `checkErrorHandler`: - - ```solidity - // function will be performed on-chain - function performUpkeep(bytes calldata performData) external { - // Decode incoming performData - (bool isError, bytes payload) = abi.decode(performData) - - // Unpacking the errorCode from checkErrorHandler - if (isError){ - (uint errorCode, bytes memory extraData, bool reportSuccess) = abi.decode( - payload, - (uint, bytes, bool) - - // Define custom logic here to handle error codes onchain - ); - } else { - // Otherwise unpacking info from checkCallback - (bytes[] memory signedReports, bytes memory extraData, bool reportSuccess) = abi.decode( - payload, - (bytes[], bytes, bool) - ); - if (reportSuccess) { - bytes memory report = signedReports[0]; - - (, bytes memory reportData) = abi.decode(report, (bytes32[3], bytes)); - - // Logic to verify and decode report - } else { - // Logic in case reports were not pulled successfully - } - } - } - ``` - -### Testing checkErrorHandler - -`checkErrorHandler` is simulated offchain. When `upkeepNeeded` returns `true`, Automation runs `performUpkeep` onchain using the `performData` from `checkErrorHandler`. If the `checkErrorHandler` function itself reverts, `performUpkeep` does not run. - -If you need to force errors in StreamsLookup while testing, you can try the following methods: - -- Specifying an incorrect `feedID` to force error code 800400 (`ErrCodeStreamsBadRequest`) -- Specifying a future timestamp to force error code 808206 (where partial content is received) for both single `feedID` and bulk `feedID` requests -- Specifying old timestamps for reports not available anymore yields either error code 808504 (no response) or 808600 (bad response), depending on which service calls the timeout request - -If your [StreamsLookup revert](/chainlink-automation/reference/automation-interfaces#streamslookup-revert) function is defined incorrectly in your smart contracts, the nodes will not be able to decode it. - -## Error codes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Error codeRetriesPossible cause of error
No errorN/ANo error
ErrCodeStreamsBadRequest: 800400NoUser requested 0 feeds
User error, incorrect parameter input
Issue with encoding http url (bad characters)
ErrCodeStreamsUnauthorized: 808401NoKey access issue or incorrect feedID
808206Log trigger - after retries; Conditional immediatelyRequested m reports but only received n (partial)
8085XX (e.g 808500)Log trigger - after retries; Conditional immediatelyNo response
ErrCodeStreamsBadResponse: 808600NoError in reading body of returned response, but service is up
ErrCodeStreamsTimeout: 808601NoNo valid report is received for 10 seconds
ErrCodeStreamsUnknownError: 808700NoUnknown
- -## Example code - -This example code includes the `revert StreamsLookup`, `checkCallback`, `checkErrorHandler` and `performUpkeep` functions. The full code example is available [here](https://github.com/smartcontractkit/documentation/blob/main/public/samples/Automation/StreamsWithError.sol). - - + diff --git a/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx b/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx new file mode 100644 index 00000000000..aa9245a559a --- /dev/null +++ b/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx @@ -0,0 +1,27 @@ +--- +section: dataStreams +date: Last Modified +title: "Using the StreamsLookup error handler" +metadata: + linkToWallet: true +excerpt: "Learn the basics for how to get data from Chainlink Data Streams." +whatsnext: { +"Find the list of available stream IDs.": "/data-streams/stream-ids", +"Find the schema of data to expect from Data Streams reports.": "/data-streams/reference/report-schema", +"Learn more about Log Trigger upkeeps": "/chainlink-automation/guides/log-trigger/", +} +--- + +import { Aside } from "@components" +import DataStreams from "@features/data-streams/common/DataStreams.astro" + + + + + + diff --git a/src/features/data-streams/common/DataStreams.astro b/src/features/data-streams/common/DataStreams.astro index 1146e9f9c5a..3cd850570c6 100644 --- a/src/features/data-streams/common/DataStreams.astro +++ b/src/features/data-streams/common/DataStreams.astro @@ -1,11 +1,14 @@ --- const GettingStarted = await Astro.glob("./gettingStarted.mdx") const GettingStartedComponent = GettingStarted[0].Content +const StreamsLookupErrorHandler = await Astro.glob("./streamsLookupErrorHandler.mdx") +const StreamsLookupErrorHandlerComponent = StreamsLookupErrorHandler[0].Content export type Props = { - section?: "gettingStarted" + section?: "gettingStarted" | "streamsLookupErrorHandler" } const { section } = Astro.props as Props --- {section === "gettingStarted" && } +{section === "streamsLookupErrorHandler" && } diff --git a/src/features/data-streams/common/streamsLookupErrorHandler.mdx b/src/features/data-streams/common/streamsLookupErrorHandler.mdx new file mode 100644 index 00000000000..ba66e9215b3 --- /dev/null +++ b/src/features/data-streams/common/streamsLookupErrorHandler.mdx @@ -0,0 +1,157 @@ +import { Aside, ClickToZoom, CodeSample } from "@components" + +The Chainlink Automation StreamsLookup error handler provides insight into potential errors or edge cases in StreamsLookup upkeeps. The table below outlines a range of error codes and the behavior associated with the codes. Use the `checkErrorHandler` function to specify how you want to respond to the error codes. `checkErrorHandler` is simulated offchain and determines what action for Automation to take onchain in `performUpkeep`. + + + +## Error handler + +When Automation detects an event, it runs the `checkLog` function, which includes a [StreamsLookup revert](/chainlink-automation/reference/automation-interfaces#streamslookup-revert) custom error. The StreamsLookup revert enables your upkeep to fetch a report from Data Streams. If reports are fetched successfully, the [`checkCallback`](/chainlink-automation/reference/automation-interfaces#checkcallback-function) function is evaluated offchain. Otherwise, the `checkErrorHandler` function is evaluated offchain to determine what Automation should do next. Both of these functions have the same output types (`bool upkeepNeeded, bytes memory performData`), which Automation uses to run `performUpkeep` onchain. The [example code](#example-code) also shows each function outlined in the diagram below: + + + +If the Automation network fails to get the requested reports, an error code is sent to the `checkErrorHandler` function in your contract. If your contract doesn't have the `checkErrorHandler` function, nothing will happen. If your contract has the `checkErrorHandler` function, it is evaluated offchain to determine what to do next. For example, you could intercept or ignore certain errors and decide not to run `performUpkeep` in those cases, in order to save time and gas. For other errors, you can execute an alternative path within `performUpkeep`, and the upkeep runs the custom logic you define in your `performUpkeep` function to handle those errors. + +1. Add the `checkErrorHandler` function in your contract to specify how you want to handle [error codes](#error-codes). For example, you could decide to ignore any codes related to bad requests or incorrect input, without running `performUpkeep` onchain: + + ```solidity + function checkErrorHandler( + uint errorCode, + bytes calldata extraData + ) external returns (bool upkeepNeeded, bytes memory performData) { + // Add custom logic to handle errors offchain here + bool _upkeepNeeded = true; + bool success = false; + bool isError = true; + if (errorCode == 800400) { + // Handle bad request error code offchain + _upkeepNeeded = false; + } else { + // logic to handle other errors + } + return (_upkeepNeeded, abi.encode(isError, abi.encode(errorCode, extraData, success))); + } + ``` + +1. Define custom logic for the alternative path within `performUpkeep`, to handle any error codes you did not intercept offchain in `checkErrorHandler`: + + ```solidity + // function will be performed on-chain + function performUpkeep(bytes calldata performData) external { + // Decode incoming performData + (bool isError, bytes payload) = abi.decode(performData) + + // Unpacking the errorCode from checkErrorHandler + if (isError){ + (uint errorCode, bytes memory extraData, bool reportSuccess) = abi.decode( + payload, + (uint, bytes, bool) + + // Define custom logic here to handle error codes onchain + ); + } else { + // Otherwise unpacking info from checkCallback + (bytes[] memory signedReports, bytes memory extraData, bool reportSuccess) = abi.decode( + payload, + (bytes[], bytes, bool) + ); + if (reportSuccess) { + bytes memory report = signedReports[0]; + + (, bytes memory reportData) = abi.decode(report, (bytes32[3], bytes)); + + // Logic to verify and decode report + } else { + // Logic in case reports were not pulled successfully + } + } + } + ``` + +### Testing checkErrorHandler + +`checkErrorHandler` is simulated offchain. When `upkeepNeeded` returns `true`, Automation runs `performUpkeep` onchain using the `performData` from `checkErrorHandler`. If the `checkErrorHandler` function itself reverts, `performUpkeep` does not run. + +If you need to force errors in StreamsLookup while testing, you can try the following methods: + +- Specifying an incorrect `feedID` to force error code 800400 (`ErrCodeStreamsBadRequest`) +- Specifying a future timestamp to force error code 808206 (where partial content is received) for both single `feedID` and bulk `feedID` requests +- Specifying old timestamps for reports not available anymore yields either error code 808504 (no response) or 808600 (bad response), depending on which service calls the timeout request + +If your [StreamsLookup revert](/chainlink-automation/reference/automation-interfaces#streamslookup-revert) function is defined incorrectly in your smart contracts, the nodes will not be able to decode it. + +## Error codes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Error codeRetriesPossible cause of error
No errorN/ANo error
ErrCodeStreamsBadRequest: 800400NoUser requested 0 feeds
User error, incorrect parameter input
Issue with encoding http url (bad characters)
ErrCodeStreamsUnauthorized: 808401NoKey access issue or incorrect feedID
808206Log trigger - after retries; Conditional immediatelyRequested m reports but only received n (partial)
8085XX (e.g 808500)Log trigger - after retries; Conditional immediatelyNo response
ErrCodeStreamsBadResponse: 808600NoError in reading body of returned response, but service is up
ErrCodeStreamsTimeout: 808601NoNo valid report is received for 10 seconds
ErrCodeStreamsUnknownError: 808700NoUnknown
+ +## Example code + +This example code includes the `revert StreamsLookup`, `checkCallback`, `checkErrorHandler` and `performUpkeep` functions. The full code example is available [here](https://github.com/smartcontractkit/documentation/blob/main/public/samples/Automation/StreamsWithError.sol). + + From 82a209259f2b561ec2fdcb90d7c7deee83685039 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:30:05 +0100 Subject: [PATCH 02/14] Update code example --- .../samples/Automation/StreamsWithError.sol | 188 ++++++++++-------- 1 file changed, 107 insertions(+), 81 deletions(-) diff --git a/public/samples/Automation/StreamsWithError.sol b/public/samples/Automation/StreamsWithError.sol index a7837446efb..d13012538b5 100644 --- a/public/samples/Automation/StreamsWithError.sol +++ b/public/samples/Automation/StreamsWithError.sol @@ -13,14 +13,26 @@ import {Common} from "@chainlink/contracts/src/v0.8/libraries/Common.sol"; * DO NOT USE THIS CODE IN PRODUCTION. */ -////////////////////////////////// -////////////////////////INTERFACES -////////////////////////////////// +// ===================== +// INTERFACES +// ===================== interface IFeeManager { + /** + * @notice Calculates the fee and reward associated with verifying a report, including discounts for subscribers. + * This function assesses the fee and reward for report verification, applying a discount for recognized subscriber addresses. + * @param subscriber The address attempting to verify the report. A discount is applied if this address + * is recognized as a subscriber. + * @param unverifiedReport The report data awaiting verification. The content of this report is used to + * determine the base fee and reward, before considering subscriber discounts. + * @param quoteAddress The payment token address used for quoting fees and rewards. + * @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable. + * @return reward The reward allocated to the caller for successfully verifying the report. + * @return totalDiscount The total discount amount deducted from the fee for subscribers. + */ function getFeeAndReward( address subscriber, - bytes memory report, + bytes memory unverifiedReport, address quoteAddress ) external returns (Common.Asset memory, Common.Asset memory, uint256); @@ -32,6 +44,15 @@ interface IFeeManager { } 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. + * @param parameterPayload Fee metadata for billing. In the current implementation, + * this consists of the abi-encoded address of the ERC-20 token used for fees. + * @return verifierResponse The encoded report from the verifier. + */ function verify( bytes calldata payload, bytes calldata parameterPayload @@ -40,9 +61,9 @@ interface IVerifierProxy { function s_feeManager() external view returns (IVerifierFeeManager); } -////////////////////////////////// -///////////////////END INTERFACES -////////////////////////////////// +// ======================= +// CONTRACT IMPLEMENTATION +// ======================= contract StreamsLookupChainlinkAutomation is ILogAutomation, @@ -89,6 +110,8 @@ contract StreamsLookupChainlinkAutomation is verifier = IVerifierProxy(_verifier); //Arbitrum Sepolia: 0x2ff010debc1297f19579b4246cad07bd24f2488a } + // This function uses revert to convey call information. + // See https://eips.ethereum.org/EIPS/eip-3668#rationale for details. function checkLog( Log calldata log, bytes memory @@ -102,107 +125,110 @@ contract StreamsLookupChainlinkAutomation is ); } + /** + * @dev This function is intended for off-chain simulation by Chainlink Automation to pass in the data reports fetched from Data Streams. + * @param values The bytes array of data reports fetched from Data Streams. + * @param extraData Contextual or additional data related to the feed lookup process. + * @return upkeepNeeded Indicates that upkeep is needed to pass the data to the on-chain performUpkeep function. + * @return performData Encoded data indicating success and including the original `values` and `extraData`, to be used in `performUpkeep`. + */ function checkCallback( bytes[] calldata values, bytes calldata extraData - ) external pure returns (bool, bytes memory) { - bool _upkeepNeeded = true; - bool success = true; - bool isError = false; - return ( - _upkeepNeeded, - abi.encode(isError, abi.encode(values, extraData, success)) - ); + ) external pure returns (bool upkeepNeeded, bytes memory) { + bool success = true; // Indicates successful data retrieval + return (true, abi.encode(success, abi.encode(values, extraData))); } + /** + * @notice Determines the need for upkeep in response to an error from Data Streams. + * @param errorCode The error code returned by the Data Streams lookup. + * @param extraData Additional context or data related to the error condition. + * @return upkeepNeeded Boolean indicating whether upkeep is needed based on the error. + * @return performData Data to be used if upkeep is performed, encoded with success state and error context. + */ function checkErrorHandler( uint errorCode, bytes calldata extraData - ) public view returns (bool upkeepNeeded, bytes memory performData) { + ) external returns (bool upkeepNeeded, bytes memory performData) { + // Add custom logic to handle errors offchain here bool _upkeepNeeded = true; bool success = false; - bool isError = true; - // Add custom logic to handle errors offchain here if (errorCode == 800400) { - // Bad request error code + // Handle bad request errors code offchain. + // In this example, no upkeep needed for bad request errors. _upkeepNeeded = false; } else { - // logic to handle other errors + // Handle other errors as needed. } return ( _upkeepNeeded, - abi.encode(isError, abi.encode(errorCode, extraData, success)) + abi.encode(success, abi.encode(errorCode, extraData)) ); } // function will be performed on-chain function performUpkeep(bytes calldata performData) external { // Decode incoming performData - (bool isError, bytes memory payload) = abi.decode( + (bool success, bytes memory payload) = abi.decode( performData, (bool, bytes) ); - // Unpacking the errorCode from checkErrorHandler - if (isError) { - (uint errorCode, bytes memory extraData, bool reportSuccess) = abi - .decode(payload, (uint, bytes, bool)); - - // Custom logic to handle error codes onchain + if (success) { + // Decode the performData bytes passed in by CL Automation. + // This contains the data returned by your implementation in checkCallback(). + (bytes[] memory signedReports, bytes memory extraData) = abi.decode( + payload, + (bytes[], bytes) + ); + // Logic to verify and decode report + bytes memory unverifiedReport = signedReports[0]; + + (, bytes memory reportData) = abi.decode( + unverifiedReport, + (bytes32[3], bytes) + ); + + // Report verification fees + IFeeManager feeManager = IFeeManager( + address(verifier.s_feeManager()) + ); + IRewardManager rewardManager = IRewardManager( + address(feeManager.i_rewardManager()) + ); + + address feeTokenAddress = feeManager.i_linkAddress(); + (Common.Asset memory fee, , ) = feeManager.getFeeAndReward( + address(this), + reportData, + feeTokenAddress + ); + + // Approve rewardManager to spend this contract's balance in fees + IERC20(feeTokenAddress).approve(address(rewardManager), fee.amount); + + // Verify the report + bytes memory verifiedReportData = verifier.verify( + unverifiedReport, + abi.encode(feeTokenAddress) + ); + + // Decode verified report data into BasicReport struct + BasicReport memory verifiedReport = abi.decode( + verifiedReportData, + (BasicReport) + ); + + // Log price from report + emit PriceUpdate(verifiedReport.price); } else { - // Otherwise unpacking info from checkCallback - ( - bytes[] memory signedReports, - bytes memory extraData, - bool reportSuccess - ) = abi.decode(payload, (bytes[], bytes, bool)); - - if (reportSuccess) { - bytes memory report = signedReports[0]; - - (, bytes memory reportData) = abi.decode( - report, - (bytes32[3], bytes) - ); - - // Billing - - IFeeManager feeManager = IFeeManager( - address(verifier.s_feeManager()) - ); - IRewardManager rewardManager = IRewardManager( - address(feeManager.i_rewardManager()) - ); - - address feeTokenAddress = feeManager.i_linkAddress(); - (Common.Asset memory fee, , ) = feeManager.getFeeAndReward( - address(this), - reportData, - feeTokenAddress - ); - - IERC20(feeTokenAddress).approve( - address(rewardManager), - fee.amount - ); - - // Verify the report - bytes memory verifiedReportData = verifier.verify( - report, - abi.encode(feeTokenAddress) - ); - - // Decode verified report data into BasicReport struct - BasicReport memory verifiedReport = abi.decode( - verifiedReportData, - (BasicReport) - ); - - // Log price from report - emit PriceUpdate(verifiedReport.price); - } else { - // Logic in case reports were not pulled successfully - } + // Handle error condition + (uint errorCode, bytes memory extraData) = abi.decode( + payload, + (uint, bytes) + ); + // Custom logic to handle error codes } } From bd1ea996bc4670ae75e54079685e7c2b40b6c9a6 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:43:25 +0100 Subject: [PATCH 03/14] Code snippets update, clarifications --- .../samples/Automation/StreamsWithError.sol | 4 +- .../common/streamsLookupErrorHandler.mdx | 51 +++++++++---------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/public/samples/Automation/StreamsWithError.sol b/public/samples/Automation/StreamsWithError.sol index d13012538b5..f8986aaf8bf 100644 --- a/public/samples/Automation/StreamsWithError.sol +++ b/public/samples/Automation/StreamsWithError.sol @@ -61,9 +61,9 @@ interface IVerifierProxy { function s_feeManager() external view returns (IVerifierFeeManager); } -// ======================= +// ========================== // CONTRACT IMPLEMENTATION -// ======================= +// ========================== contract StreamsLookupChainlinkAutomation is ILogAutomation, diff --git a/src/features/data-streams/common/streamsLookupErrorHandler.mdx b/src/features/data-streams/common/streamsLookupErrorHandler.mdx index ba66e9215b3..a930fda3427 100644 --- a/src/features/data-streams/common/streamsLookupErrorHandler.mdx +++ b/src/features/data-streams/common/streamsLookupErrorHandler.mdx @@ -25,6 +25,13 @@ If the Automation network fails to get the requested reports, an error code is s 1. Add the `checkErrorHandler` function in your contract to specify how you want to handle [error codes](#error-codes). For example, you could decide to ignore any codes related to bad requests or incorrect input, without running `performUpkeep` onchain: ```solidity + /** + * @notice Determines the need for upkeep in response to an error from Data Streams. + * @param errorCode The error code returned by the Data Streams lookup. + * @param extraData Additional context or data related to the error condition. + * @return upkeepNeeded Boolean indicating whether upkeep is needed based on the error. + * @return performData Data to be used if upkeep is performed, encoded with success state and error context. + */ function checkErrorHandler( uint errorCode, bytes calldata extraData @@ -32,14 +39,14 @@ If the Automation network fails to get the requested reports, an error code is s // Add custom logic to handle errors offchain here bool _upkeepNeeded = true; bool success = false; - bool isError = true; if (errorCode == 800400) { - // Handle bad request error code offchain + // Handle bad request errors code offchain. + // In this example, no upkeep needed for bad request errors. _upkeepNeeded = false; } else { - // logic to handle other errors + // Handle other errors as needed. } - return (_upkeepNeeded, abi.encode(isError, abi.encode(errorCode, extraData, success))); + return (_upkeepNeeded, abi.encode(success, abi.encode(errorCode, extraData))); } ``` @@ -49,31 +56,18 @@ If the Automation network fails to get the requested reports, an error code is s // function will be performed on-chain function performUpkeep(bytes calldata performData) external { // Decode incoming performData - (bool isError, bytes payload) = abi.decode(performData) - - // Unpacking the errorCode from checkErrorHandler - if (isError){ - (uint errorCode, bytes memory extraData, bool reportSuccess) = abi.decode( - payload, - (uint, bytes, bool) - - // Define custom logic here to handle error codes onchain - ); - } else { - // Otherwise unpacking info from checkCallback - (bytes[] memory signedReports, bytes memory extraData, bool reportSuccess) = abi.decode( - payload, - (bytes[], bytes, bool) - ); - if (reportSuccess) { - bytes memory report = signedReports[0]; - - (, bytes memory reportData) = abi.decode(report, (bytes32[3], bytes)); + (bool success, bytes memory payload) = abi.decode(performData, (bool, bytes)); + if (success) { + // Decode the performData bytes passed in by CL Automation. + // This contains the data returned by your implementation in checkCallback(). + (bytes[] memory signedReports, bytes memory extraData) = abi.decode(payload, (bytes[], bytes)); // Logic to verify and decode report - } else { - // Logic in case reports were not pulled successfully - } + // ... + } else { + // Handle error condition + (uint errorCode, bytes memory extraData) = abi.decode(payload, (uint, bytes)); + // Custom logic to handle error codes } } ``` @@ -84,7 +78,8 @@ If the Automation network fails to get the requested reports, an error code is s If you need to force errors in StreamsLookup while testing, you can try the following methods: -- Specifying an incorrect `feedID` to force error code 800400 (`ErrCodeStreamsBadRequest`) +- Not specifying any `feedID` to force error code 800400 (`ErrCodeStreamsBadRequest`) +- Specifying an incorrect `feedID` to force error code 800401 (`ErrCodeStreamsBadRequest`) - Specifying a future timestamp to force error code 808206 (where partial content is received) for both single `feedID` and bulk `feedID` requests - Specifying old timestamps for reports not available anymore yields either error code 808504 (no response) or 808600 (bad response), depending on which service calls the timeout request From cd75252bca07bf56611f4c561fcaafe961a86464 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:09:01 +0100 Subject: [PATCH 04/14] Small fixes --- public/samples/Automation/StreamsWithError.sol | 12 ++++++------ .../tutorials/streams-lookup-error-handler.mdx | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/public/samples/Automation/StreamsWithError.sol b/public/samples/Automation/StreamsWithError.sol index df2d7edf63f..4e26364031f 100644 --- a/public/samples/Automation/StreamsWithError.sol +++ b/public/samples/Automation/StreamsWithError.sol @@ -136,8 +136,8 @@ contract StreamsLookupChainlinkAutomation is bytes[] calldata values, bytes calldata extraData ) external pure returns (bool upkeepNeeded, bytes memory) { - bool success = true; // Indicates successful data retrieval - return (true, abi.encode(success, abi.encode(values, extraData))); + bool reportSuccess = true; // Indicates successful data retrieval + return (true, abi.encode(reportSuccess, abi.encode(values, extraData))); } /** @@ -153,7 +153,7 @@ contract StreamsLookupChainlinkAutomation is ) external returns (bool upkeepNeeded, bytes memory performData) { // Add custom logic to handle errors offchain here bool _upkeepNeeded = true; - bool success = false; + bool reportSuccess = false; if (errorCode == 808400) { // Handle bad request errors code offchain. // In this example, no upkeep needed for bad request errors. @@ -163,19 +163,19 @@ contract StreamsLookupChainlinkAutomation is } return ( _upkeepNeeded, - abi.encode(success, abi.encode(errorCode, extraData)) + abi.encode(reportSuccess, abi.encode(errorCode, extraData)) ); } // function will be performed on-chain function performUpkeep(bytes calldata performData) external { // Decode incoming performData - (bool success, bytes memory payload) = abi.decode( + (bool reportSuccess, bytes memory payload) = abi.decode( performData, (bool, bytes) ); - if (success) { + if (reportSuccess) { // Decode the performData bytes passed in by CL Automation. // This contains the data returned by your implementation in checkCallback(). (bytes[] memory signedReports, bytes memory extraData) = abi.decode( diff --git a/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx b/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx index aa9245a559a..b9556df1fc5 100644 --- a/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx +++ b/src/content/data-streams/tutorials/streams-lookup-error-handler.mdx @@ -4,7 +4,6 @@ date: Last Modified title: "Using the StreamsLookup error handler" metadata: linkToWallet: true -excerpt: "Learn the basics for how to get data from Chainlink Data Streams." whatsnext: { "Find the list of available stream IDs.": "/data-streams/stream-ids", "Find the schema of data to expect from Data Streams reports.": "/data-streams/reference/report-schema", From 0e6c4f4500c7a477e580a0eab680d3e3b11f1d7f Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:13:23 +0100 Subject: [PATCH 05/14] Small fixes --- .../data-streams/common/streamsLookupErrorHandler.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/data-streams/common/streamsLookupErrorHandler.mdx b/src/features/data-streams/common/streamsLookupErrorHandler.mdx index b0fad4fc3e9..977b2e633a1 100644 --- a/src/features/data-streams/common/streamsLookupErrorHandler.mdx +++ b/src/features/data-streams/common/streamsLookupErrorHandler.mdx @@ -38,7 +38,7 @@ If the Automation network fails to get the requested reports, an error code is s ) external returns (bool upkeepNeeded, bytes memory performData) { // Add custom logic to handle errors offchain here bool _upkeepNeeded = true; - bool success = false; + bool reportSuccess = false; if (errorCode == 808400) { // Handle bad request errors code offchain. // In this example, no upkeep needed for bad request errors. @@ -46,7 +46,7 @@ If the Automation network fails to get the requested reports, an error code is s } else { // Handle other errors as needed. } - return (_upkeepNeeded, abi.encode(success, abi.encode(errorCode, extraData))); + return (_upkeepNeeded, abi.encode(reportSuccess, abi.encode(errorCode, extraData))); } ``` @@ -56,9 +56,9 @@ If the Automation network fails to get the requested reports, an error code is s // function will be performed on-chain function performUpkeep(bytes calldata performData) external { // Decode incoming performData - (bool success, bytes memory payload) = abi.decode(performData, (bool, bytes)); + (bool reportSuccess, bytes memory payload) = abi.decode(performData, (bool, bytes)); - if (success) { + if (reportSuccess) { // Decode the performData bytes passed in by CL Automation. // This contains the data returned by your implementation in checkCallback(). (bytes[] memory signedReports, bytes memory extraData) = abi.decode(payload, (bytes[], bytes)); From 3d8a56b640d4f4e30b7f1b825c362cf12674e7d8 Mon Sep 17 00:00:00 2001 From: Karim <98668332+khadni@users.noreply.github.com> Date: Fri, 7 Jun 2024 08:52:45 -0500 Subject: [PATCH 06/14] Update StreamsUpkeepWithErrorHandler --- .../StreamsUpkeepWithErrorHandler.sol} | 55 +++++++++++-------- .../guides/streams-lookup-error-handler.mdx | 11 ++-- .../common/streamsLookupErrorHandler.mdx | 4 +- 3 files changed, 40 insertions(+), 30 deletions(-) rename public/samples/{Automation/StreamsWithError.sol => DataStreams/StreamsUpkeepWithErrorHandler.sol} (85%) diff --git a/public/samples/Automation/StreamsWithError.sol b/public/samples/DataStreams/StreamsUpkeepWithErrorHandler.sol similarity index 85% rename from public/samples/Automation/StreamsWithError.sol rename to public/samples/DataStreams/StreamsUpkeepWithErrorHandler.sol index 479caf4391e..451900955e0 100644 --- a/public/samples/Automation/StreamsWithError.sol +++ b/public/samples/DataStreams/StreamsUpkeepWithErrorHandler.sol @@ -65,21 +65,11 @@ interface IVerifierProxy { // CONTRACT IMPLEMENTATION // ========================== -contract StreamsLookupChainlinkAutomation is +contract StreamsUpkeepWithErrorHandler is ILogAutomation, StreamsLookupCompatibleInterface { - struct BasicReport { - bytes32 feedId; // The feed ID the report has data for - uint32 validFromTimestamp; // Earliest timestamp for which price is applicable - uint32 observationsTimestamp; // Latest timestamp for which price is applicable - uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (WETH/ETH) - uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK - uint32 expiresAt; // Latest timestamp where the report can be verified on-chain - int192 price; // DON consensus median price, carried to 18 decimal places - } - - struct PremiumReport { + struct Report { bytes32 feedId; // The feed ID the report has data for uint32 validFromTimestamp; // Earliest timestamp for which price is applicable uint32 observationsTimestamp; // Latest timestamp for which price is applicable @@ -96,14 +86,17 @@ contract StreamsLookupChainlinkAutomation is } event PriceUpdate(int192 indexed price); + event ErrorTestLog(uint indexed errorCode); IVerifierProxy public verifier; address public FEE_ADDRESS; string public constant STRING_DATASTREAMS_FEEDLABEL = "feedIDs"; string public constant STRING_DATASTREAMS_QUERYLABEL = "timestamp"; + uint256 public s_error; + bool public s_isError; string[] public feedIds = [ - "0x00027bbaff688c906a3e20a34fe951715d1018d262a5b66e38eda027a674cd1b" // Ex. Basic ETH/USD price report + "0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782" // Ex. ETH/USD Feed ID ]; constructor(address _verifier) { @@ -142,6 +135,9 @@ contract StreamsLookupChainlinkAutomation is /** * @notice Determines the need for upkeep in response to an error from Data Streams. + * @dev This function serves as an example of how errors can be handled offchain. + * @dev Developers can parameterize this logic as needed. + * @dev All error codes are documented at: https://docs.chain.link/chainlink-automation/guides/streams-lookup-error-handler#error-codes * @param errorCode The error code returned by the Data Streams lookup. * @param extraData Additional context or data related to the error condition. * @return upkeepNeeded Boolean indicating whether upkeep is needed based on the error. @@ -150,16 +146,24 @@ contract StreamsLookupChainlinkAutomation is function checkErrorHandler( uint errorCode, bytes calldata extraData - ) external returns (bool upkeepNeeded, bytes memory performData) { - // Add custom logic to handle errors offchain here - bool _upkeepNeeded = true; + ) external view returns (bool upkeepNeeded, bytes memory performData) { + bool _upkeepNeeded = false; bool reportSuccess = false; - if (errorCode == 808400) { - // Handle bad request errors code offchain. - // In this example, no upkeep needed for bad request errors. - _upkeepNeeded = false; + if (errorCode == 0) { + // If there is no error, proceed with the performUpkeep and + // the report decoding/verification + _upkeepNeeded = true; + reportSuccess = true; + } else if (errorCode == 808400 || errorCode == 808401) { + // Mark upkeep as needed for bad requests (808400) and incorrect feed ID (808401) + // to handle these specific errors onchain. + _upkeepNeeded = true; + // Note that reportSuccess remains false. } else { - // Handle other errors as needed. + // For other error codes, decide not to perform upkeep. + // This is the default behavior, explicitly noted for clarity in this example. + _upkeepNeeded = false; + reportSuccess = false; } return ( _upkeepNeeded, @@ -167,7 +171,6 @@ contract StreamsLookupChainlinkAutomation is ); } - // function will be performed on-chain function performUpkeep(bytes calldata performData) external { // Decode incoming performData (bool reportSuccess, bytes memory payload) = abi.decode( @@ -215,9 +218,9 @@ contract StreamsLookupChainlinkAutomation is ); // Decode verified report data into BasicReport struct - BasicReport memory verifiedReport = abi.decode( + Report memory verifiedReport = abi.decode( verifiedReportData, - (BasicReport) + (Report) ); // Log price from report @@ -229,8 +232,12 @@ contract StreamsLookupChainlinkAutomation is (uint, bytes) ); // Custom logic to handle error codes + s_error = errorCode; + s_isError = true; } } fallback() external payable {} + + receive() external payable {} } diff --git a/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx b/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx index 5a74e9de694..53c344b0319 100644 --- a/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx +++ b/src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx @@ -11,10 +11,13 @@ whatsnext: { "Troubleshoot and Debug Upkeeps": "/chainlink-automation/reference/ import { Aside } from "@components" import DataStreams from "@features/data-streams/common/DataStreams.astro" -