-
Notifications
You must be signed in to change notification settings - Fork 406
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLA StreamsLookup ErrorHandler (#1795)
* CLA StreamsLookup ErrorHandler * table mishap * table mishap 2 * formatting * Add testing errorhandler section * Swap out diagrams * Swap vertical diagram and text/table edits * Add to sidebar, table adjustments * shorter title * Table anchor links not really working * Add sample and temp link; remove table anchor links * Add links to interfaces page * Draft of interface page update * Swap diagram; code edits * Edits from review * Remove unused diagrams * Delete more interfaces * Another code adjustment * Update link and minor edit * More clarifications * Code snippet correction * Apply suggestions from code review Co-authored-by: Dwight Lyle <dwightjl@gmail.com> * Apply suggestions from code review Co-authored-by: Karim H. <98668332+khadni@users.noreply.github.com> * Update src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx Co-authored-by: Karim H. <98668332+khadni@users.noreply.github.com> * Code changes to conditionally unpack performData * Adjust table * abi.decode correction * Code comment clarity * Fix indentation --------- Co-authored-by: Dwight Lyle <dwightjl@gmail.com> Co-authored-by: Karim H. <98668332+khadni@users.noreply.github.com>
- Loading branch information
1 parent
4c095b6
commit 7e443fa
Showing
5 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.16; | ||
|
||
import {StreamsLookupCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/interfaces/StreamsLookupCompatibleInterface.sol"; | ||
import {ILogAutomation, Log} from "@chainlink/contracts/src/v0.8/automation/interfaces/ILogAutomation.sol"; | ||
import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol"; | ||
import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol"; | ||
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol"; | ||
import {Common} from "@chainlink/contracts/src/v0.8/libraries/Common.sol"; | ||
|
||
/** | ||
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. | ||
* DO NOT USE THIS CODE IN PRODUCTION. | ||
*/ | ||
|
||
////////////////////////////////// | ||
////////////////////////INTERFACES | ||
////////////////////////////////// | ||
|
||
interface IFeeManager { | ||
function getFeeAndReward( | ||
address subscriber, | ||
bytes memory report, | ||
address quoteAddress | ||
) external returns (Common.Asset memory, Common.Asset memory, uint256); | ||
|
||
function i_linkAddress() external view returns (address); | ||
|
||
function i_nativeAddress() external view returns (address); | ||
|
||
function i_rewardManager() external view returns (address); | ||
} | ||
|
||
interface IVerifierProxy { | ||
function verify( | ||
bytes calldata payload, | ||
bytes calldata parameterPayload | ||
) external payable returns (bytes memory verifierResponse); | ||
|
||
function s_feeManager() external view returns (IVerifierFeeManager); | ||
} | ||
|
||
////////////////////////////////// | ||
///////////////////END INTERFACES | ||
////////////////////////////////// | ||
|
||
contract StreamsLookupChainlinkAutomation 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 { | ||
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 | ||
int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation | ||
int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation | ||
} | ||
|
||
struct Quote { | ||
address quoteAddress; | ||
} | ||
|
||
event PriceUpdate(int192 indexed price); | ||
|
||
IVerifierProxy public verifier; | ||
|
||
address public FEE_ADDRESS; | ||
string public constant STRING_DATASTREAMS_FEEDLABEL = "feedIDs"; | ||
string public constant STRING_DATASTREAMS_QUERYLABEL = "timestamp"; | ||
string[] public feedIds = [ | ||
"0x00027bbaff688c906a3e20a34fe951715d1018d262a5b66e38eda027a674cd1b" // Ex. Basic ETH/USD price report | ||
]; | ||
|
||
constructor(address _verifier) { | ||
verifier = IVerifierProxy(_verifier); //Arbitrum Sepolia: 0x2ff010debc1297f19579b4246cad07bd24f2488a | ||
} | ||
|
||
function checkLog( | ||
Log calldata log, | ||
bytes memory | ||
) external returns (bool upkeepNeeded, bytes memory performData) { | ||
revert StreamsLookup( | ||
STRING_DATASTREAMS_FEEDLABEL, | ||
feedIds, | ||
STRING_DATASTREAMS_QUERYLABEL, | ||
log.timestamp, | ||
"" | ||
); | ||
} | ||
|
||
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)) | ||
); | ||
} | ||
|
||
function checkErrorHandler( | ||
uint errorCode, | ||
bytes calldata extraData | ||
) public view returns (bool upkeepNeeded, bytes memory performData) { | ||
bool _upkeepNeeded = true; | ||
bool success = false; | ||
bool isError = true; | ||
// Add custom logic to handle errors offchain here | ||
if (errorCode == 800400) { | ||
// Bad request error code | ||
_upkeepNeeded = false; | ||
} else { | ||
// logic to handle other errors | ||
} | ||
return ( | ||
_upkeepNeeded, | ||
abi.encode(isError, abi.encode(errorCode, extraData, success)) | ||
); | ||
} | ||
|
||
// function will be performed on-chain | ||
function performUpkeep(bytes calldata performData) external { | ||
// Decode incoming performData | ||
(bool isError, 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 | ||
} 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 | ||
} | ||
} | ||
} | ||
|
||
fallback() external payable {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
src/content/chainlink-automation/guides/streams-lookup-error-handler.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
--- | ||
section: automation | ||
date: Last Modified | ||
title: "Using the StreamsLookup error handler" | ||
isMdx: true | ||
whatsnext: { "Troubleshoot and Debug Upkeeps": "/chainlink-automation/reference/debugging-errors", | ||
"Automation Interfaces": "/chainlink-automation/reference/automation-interfaces", | ||
"Automation Contracts": "/chainlink-automation/reference/automation-contracts" } | ||
--- | ||
|
||
import { Aside, ClickToZoom, CodeSample } from "@components" | ||
|
||
<Aside type="note" title="Data Streams Mainnet Access"> | ||
Chainlink Data Streams is available on Arbitrum Mainnet and Arbitrum Sepolia. [Contact | ||
us](https://chainlinkcommunity.typeform.com/datastreams?#ref_id=docs) to talk to an expert about integrating Chainlink | ||
Data Streams with your applications. | ||
</Aside> | ||
|
||
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`. | ||
|
||
<Aside type="caution" title="Developer responsibility"> | ||
Developers implementing Chainlink products are solely responsible for maintaining the security and user experience of | ||
their applications. Developers must monitor and mitigate any potential application code risks that may, among other | ||
things, result in unanticipated application behavior, including by instituting requisite [risk mitigation | ||
processes](/data-feeds/selecting-data-feeds#risk-mitigation) including, but not limited to, data quality checks, | ||
circuit breakers, and appropriate contingency logic for their use case. | ||
</Aside> | ||
|
||
## 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: | ||
|
||
<ClickToZoom | ||
src="/images/automation/streamslookup-errorhandler-horizontal.png" | ||
alt="Error handler flow diagram" | ||
style="display: block; margin-left: auto; margin-right: auto;" | ||
/> | ||
|
||
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 | ||
|
||
<table> | ||
<thead> | ||
<tr> | ||
<th>Error code</th> | ||
<th>Retries</th> | ||
<th>Possible cause of error</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td>No error</td> | ||
<td>N/A</td> | ||
<td>No error</td> | ||
</tr> | ||
<tr> | ||
<td rowspan="3">ErrCodeStreamsBadRequest: 800400</td> | ||
<td rowspan="3">No</td> | ||
<td>User requested 0 feeds</td> | ||
</tr> | ||
<tr> | ||
<td>User error, incorrect parameter input</td> | ||
</tr> | ||
<tr> | ||
<td>Issue with encoding http url (bad characters)</td> | ||
</tr> | ||
<tr> | ||
<td>ErrCodeStreamsUnauthorized: 808401</td> | ||
<td>No</td> | ||
<td>Key access issue or incorrect feedID</td> | ||
</tr> | ||
<tr> | ||
<td>808206</td> | ||
<td>Log trigger - after retries; Conditional immediately</td> | ||
<td>Requested m reports but only received n (partial)</td> | ||
</tr> | ||
<tr> | ||
<td>8085XX (e.g 808500)</td> | ||
<td>Log trigger - after retries; Conditional immediately</td> | ||
<td>No response</td> | ||
</tr> | ||
<tr id="808600"> | ||
<td>ErrCodeStreamsBadResponse: 808600</td> | ||
<td>No</td> | ||
<td>Error in reading body of returned response, but service is up</td> | ||
</tr> | ||
<tr id="808601"> | ||
<td>ErrCodeStreamsTimeout: 808601</td> | ||
<td>No</td> | ||
<td>No valid report is received for 10 seconds</td> | ||
</tr> | ||
<tr id="808700"> | ||
<td>ErrCodeStreamsUnknownError: 808700</td> | ||
<td>No</td> | ||
<td>Unknown</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
|
||
## 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). | ||
|
||
<CodeSample src="samples/Automation/StreamsWithError.sol" /> |
Oops, something went wrong.