Skip to content

Commit

Permalink
Hardhat Getting Started Guide (#1824)
Browse files Browse the repository at this point in the history
* init

* link fix

* sidebar update

* nit

* links fix

* links fix

* Refactor deployment scripts into Hardhat tasks + clarifications

* Apply suggestions from code review

Co-authored-by: Amine E. <aelmanaa@users.noreply.github.com>

* nit

* nit

* nit

* Update links to Hardhat repo

* Update links to Hardhat repo

* Clarifications

* Apply suggestions from code review

Co-authored-by: Dwight Lyle <dwightjl@gmail.com>

* Links fix

---------

Co-authored-by: Amine E. <aelmanaa@users.noreply.github.com>
Co-authored-by: Dwight Lyle <dwightjl@gmail.com>
  • Loading branch information
3 people authored Mar 20, 2024
1 parent d3fab2c commit a6978a5
Show file tree
Hide file tree
Showing 6 changed files with 486 additions and 3 deletions.
232 changes: 232 additions & 0 deletions public/samples/DataStreams/StreamsUpkeepRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {Common} from "@chainlink/contracts/src/v0.8/libraries/Common.sol";
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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE FOR DEMONSTRATION PURPOSES.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

/**
* @dev Defines the parameters required to register a new upkeep.
* @param name The name of the upkeep to be registered.
* @param encryptedEmail An encrypted email address associated with the upkeep (optional).
* @param upkeepContract The address of the contract that requires upkeep.
* @param gasLimit The maximum amount of gas to be used for the upkeep execution.
* @param adminAddress The address that will have administrative privileges over the upkeep.
* @param triggerType An identifier for the type of trigger that initiates the upkeep (`1` for event-based).
* @param checkData Data passed to the checkUpkeep function to simulate conditions for triggering upkeep.
* @param triggerConfig Configuration parameters specific to the trigger type.
* @param offchainConfig Off-chain configuration data, if applicable.
* @param amount The amount of LINK tokens to fund the upkeep registration.
*/
struct RegistrationParams {
string name;
bytes encryptedEmail;
address upkeepContract;
uint32 gasLimit;
address adminAddress;
uint8 triggerType;
bytes checkData;
bytes triggerConfig;
bytes offchainConfig;
uint96 amount;
}

/**
* @dev Interface for the Automation Registrar contract.
*/
interface AutomationRegistrarInterface {
/**
* @dev Registers a new upkeep contract with Chainlink Automation.
* @param requestParams The parameters required for the upkeep registration, encapsulated in `RegistrationParams`.
* @return upkeepID The unique identifier for the registered upkeep, used for future interactions.
*/
function registerUpkeep(
RegistrationParams calldata requestParams
) external returns (uint256);
}

// Custom interfaces for Data Streams: IVerifierProxy and IFeeManager
interface IVerifierProxy {
function verify(
bytes calldata payload,
bytes calldata parameterPayload
) external payable returns (bytes memory verifierResponse);

function s_feeManager() external view returns (IVerifierFeeManager);
}

interface IFeeManager {
function getFeeAndReward(
address subscriber,
bytes memory unverifiedReport,
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);
}

contract StreamsUpkeepRegistrar is
ILogAutomation,
StreamsLookupCompatibleInterface
{
LinkTokenInterface public immutable i_link;
AutomationRegistrarInterface public immutable i_registrar;

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 onchain
int192 price; // DON consensus median price, carried to 8 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 onchain
int192 price; // DON consensus median price, carried to 8 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 DATASTREAMS_FEEDLABEL = "feedIDs";
string public constant DATASTREAMS_QUERYLABEL = "timestamp";
int192 public s_last_retrieved_price;
uint256 s_upkeepID;
bytes public s_LogTriggerConfig;

// Find a complete list of IDs at https://docs.chain.link/data-streams/stream-ids
string[] public feedIds;

constructor(
address _verifier,
LinkTokenInterface link,
AutomationRegistrarInterface registrar,
string[] memory _feedIds
) {
verifier = IVerifierProxy(_verifier);
i_link = link;
i_registrar = registrar;
feedIds = _feedIds;
}

/**
* @notice Registers a new upkeep using the specified parameters and predicts its ID.
* @dev This function first approves the transfer of LINK tokens specified in `params.amount` to the Automation Registrar contract.
* It then registers the upkeep and stores its ID if registration is successful.
* Reverts if auto-approve is disabled or registration fails.
* @param params The registration parameters, including name, upkeep contract address, gas limit, admin address, trigger type, and funding amount.
*/
function registerAndPredictID(RegistrationParams memory params) public {
i_link.approve(address(i_registrar), params.amount);
uint256 upkeepID = i_registrar.registerUpkeep(params);
if (upkeepID != 0) {
s_upkeepID = upkeepID; // DEV - Use the upkeepID however you see fit
} else {
revert("auto-approve disabled");
}
}

// 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
) external returns (bool upkeepNeeded, bytes memory performData) {
revert StreamsLookup(
DATASTREAMS_FEEDLABEL,
feedIds,
DATASTREAMS_QUERYLABEL,
log.timestamp,
""
);
}

// The Data Streams report bytes is passed here.
// extraData is context data from feed lookup process.
// Your contract may include logic to further process this data.
// This method is intended only to be simulated offchain by Automation.
// The data returned will then be passed by Automation into performUpkeep
function checkCallback(
bytes[] calldata values,
bytes calldata extraData
) external pure returns (bool, bytes memory) {
return (true, abi.encode(values, extraData));
}

// function will be performed onchain
function performUpkeep(bytes calldata performData) external {
// 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(
performData,
(bytes[], bytes)
);

bytes memory unverifiedReport = signedReports[0];

(, /* bytes32[3] reportContextData */ 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);

// Store the price from the report
s_last_retrieved_price = verifiedReport.price;
}
}
6 changes: 5 additions & 1 deletion src/config/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,13 @@ export const SIDEBAR: Partial<Record<Sections, SectionEntry[]>> = {
url: "data-streams",
},
{
title: "Getting Started",
title: "Getting Started (Remix IDE)",
url: "data-streams/getting-started",
},
{
title: "Getting Started (Hardhat CLI)",
url: "data-streams/getting-started-hardhat",
},
{
title: "Stream IDs",
url: "data-streams/stream-ids",
Expand Down
27 changes: 27 additions & 0 deletions src/content/data-streams/getting-started-hardhat.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
section: dataStreams
date: Last Modified
title: "Getting Started with Chainlink Data Streams using the Hardhat CLI"
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"

<Aside type="note" title="Mainnet Access">
Chainlink Data Streams is available on Arbitrum Mainnet and Arbitrum Sepolia.
</Aside>

<Aside type="note" title="Talk to an expert">
<a href="https://chainlinkcommunity.typeform.com/datastreams?#ref_id=docs">Contact us</a> to talk to an expert about
integrating Chainlink Data Streams with your applications.
</Aside>

<DataStreams section="gettingStartedHardhat" />
5 changes: 4 additions & 1 deletion src/features/data-streams/common/DataStreams.astro
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
---
const GettingStarted = await Astro.glob("./gettingStarted.mdx")
const GettingStartedComponent = GettingStarted[0].Content
const GettingStartedHardhat = await Astro.glob("./gettingStartedHardhat.mdx")
const GettingStartedHardhatComponent = GettingStartedHardhat[0].Content
export type Props = {
section?: "gettingStarted"
section?: "gettingStarted" | "gettingStartedHardhat"
}
const { section } = Astro.props as Props
---

{section === "gettingStarted" && <GettingStartedComponent />}
{section === "gettingStartedHardhat" && <GettingStartedHardhatComponent />}
4 changes: 3 additions & 1 deletion src/features/data-streams/common/gettingStarted.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Aside, CodeSample, CopyText, ClickToZoom } from "@components"

This guide shows you how to read data from a Data Stream, validate the answer, and store the answer onchain. This example uses a [Chainlink Automation Log Trigger](/chainlink-automation/guides/log-trigger) to check for events that require data. For this example, the log trigger comes from a simple emitter contract. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Engine, return the data in a callback, and run the [`performUpkeep` function](/chainlink-automation/reference/automation-interfaces#performupkeep-function-for-log-triggers) on your registered upkeep contract. The `performUpkeep` function calls the `verify` function on the verifier contract.
This guide shows you how to read data from a Data Streams feed, validate the answer, and store the answer onchain. This guide uses the [Remix IDE](https://remix-project.org/) so you can complete these steps in a web-based development environment. If you prefer to complete these steps using terminal commands, read the [Getting Started - Hardhat CLI](/data-streams/getting-started-hardhat) guide instead.

This example uses a [Chainlink Automation Log Trigger](/chainlink-automation/guides/log-trigger) to check for events that require data. For this example, the log trigger comes from a simple emitter contract. Chainlink Automation then uses `StreamsLookup` to retrieve a signed report from the Data Streams Engine, return the data in a callback, and run the [`performUpkeep` function](/chainlink-automation/reference/automation-interfaces#performupkeep-function-for-log-triggers) on your registered upkeep contract. The `performUpkeep` function calls the `verify` function on the verifier contract.

<Aside type="caution" title="Disclaimer">
This guide represents an example of using a Chainlink product or service and is provided to help you understand how to
Expand Down
Loading

0 comments on commit a6978a5

Please sign in to comment.