Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hardhat Getting Started Guide #1824

Merged
merged 19 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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">
khadni marked this conversation as resolved.
Show resolved Hide resolved
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
Loading