Skip to content

Commit 04bf028

Browse files
authored
Datalink: Initial docs (#3028)
1 parent b92ba48 commit 04bf028

36 files changed

+3136
-6
lines changed
104 KB
Loading
141 KB
Loading
181 KB
Loading
7.62 KB
Loading
25 KB
Loading
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.19;
3+
4+
import {Common} from "@chainlink/contracts/src/v0.8/llo-feeds/libraries/Common.sol";
5+
import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol";
6+
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
7+
import {SafeERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
8+
9+
using SafeERC20 for IERC20;
10+
11+
/**
12+
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE FOR DEMONSTRATION PURPOSES.
13+
* DO NOT USE THIS CODE IN PRODUCTION.
14+
*
15+
* This contract can verify Chainlink DataLink reports onchain and pay
16+
* the verification fee in LINK (when required).
17+
*
18+
* - If `VerifierProxy.s_feeManager()` returns a non-zero address, the network
19+
* expects you to interact with that FeeManager for every verification call:
20+
* quote fees, approve the RewardManager, then call `verify()`.
21+
*
22+
* - If `s_feeManager()` returns the zero address, no FeeManager contract has
23+
* been deployed on that chain. In that case there is nothing to quote or pay
24+
* onchain, so the contract skips the fee logic entirely.
25+
*
26+
* The `if (address(feeManager) != address(0))` check below chooses the
27+
* correct path automatically, making the same bytecode usable on any chain.
28+
*/
29+
30+
// ────────────────────────────────────────────────────────────────────────────
31+
// Interfaces
32+
// ────────────────────────────────────────────────────────────────────────────
33+
34+
interface IVerifierProxy {
35+
/**
36+
* @notice Route a report to the correct verifier and (optionally) bill fees.
37+
* @param payload Full report payload (header + signed report).
38+
* @param parameterPayload ABI-encoded fee metadata.
39+
*/
40+
function verify(
41+
bytes calldata payload,
42+
bytes calldata parameterPayload
43+
) external payable returns (bytes memory verifierResponse);
44+
45+
function verifyBulk(
46+
bytes[] calldata payloads,
47+
bytes calldata parameterPayload
48+
) external payable returns (bytes[] memory verifiedReports);
49+
50+
function s_feeManager() external view returns (IVerifierFeeManager);
51+
}
52+
53+
interface IFeeManager {
54+
/**
55+
* @return fee, reward, totalDiscount
56+
*/
57+
function getFeeAndReward(
58+
address subscriber,
59+
bytes memory unverifiedReport,
60+
address quoteAddress
61+
) external returns (Common.Asset memory, Common.Asset memory, uint256);
62+
63+
function i_linkAddress() external view returns (address);
64+
65+
function i_nativeAddress() external view returns (address);
66+
67+
function i_rewardManager() external view returns (address);
68+
}
69+
70+
// ────────────────────────────────────────────────────────────────────────────
71+
// Contract
72+
// ────────────────────────────────────────────────────────────────────────────
73+
74+
/**
75+
* @dev This contract implements functionality to verify DataLink reports from
76+
* the API, with payment in LINK tokens.
77+
*/
78+
contract ClientReportsVerifier {
79+
// ----------------- Errors -----------------
80+
error NothingToWithdraw();
81+
error NotOwner(address caller);
82+
error InvalidReportVersion(uint16 version);
83+
84+
// ----------------- Report schemas -----------------
85+
// Extract schema version from feed ID (first 2 bytes of the feed ID)
86+
/**
87+
* @dev DataLink report schema v3.
88+
* Prices, bids and asks use 8 or 18 decimals depending on the feed.
89+
*/
90+
struct ReportV3 {
91+
bytes32 feedId;
92+
uint32 validFromTimestamp;
93+
uint32 observationsTimestamp;
94+
uint192 nativeFee;
95+
uint192 linkFee;
96+
uint32 expiresAt;
97+
int192 price;
98+
int192 bid;
99+
int192 ask;
100+
}
101+
102+
/**
103+
* @dev DataLink report schema v4.
104+
*/
105+
struct ReportV4 {
106+
bytes32 feedId;
107+
uint32 validFromTimestamp;
108+
uint32 observationsTimestamp;
109+
uint192 nativeFee;
110+
uint192 linkFee;
111+
uint32 expiresAt;
112+
int192 price;
113+
uint32 marketStatus;
114+
}
115+
116+
// ----------------- Storage -----------------
117+
IVerifierProxy public immutable i_verifierProxy;
118+
address private immutable i_owner;
119+
120+
int192 public lastDecodedPrice;
121+
122+
// ----------------- Events -----------------
123+
event DecodedPrice(int192 price);
124+
125+
// ----------------- Constructor / modifier -----------------
126+
/**
127+
* @param _verifierProxy Address of the VerifierProxy on the target network.
128+
* Addresses: https://docs.chain.link/datalink/pull-delivery/verifier-proxy-addresses
129+
*/
130+
constructor(address _verifierProxy) {
131+
i_owner = msg.sender;
132+
i_verifierProxy = IVerifierProxy(_verifierProxy);
133+
}
134+
135+
modifier onlyOwner() {
136+
if (msg.sender != i_owner) revert NotOwner(msg.sender);
137+
_;
138+
}
139+
140+
// ----------------- Public API -----------------
141+
142+
/**
143+
* @notice Verify a DataLink report (schema v3 or v4).
144+
*
145+
* @dev Steps:
146+
* 1. Decode the unverified report to get `reportData`.
147+
* 2. Read the first two bytes → schema version (`0x0003` or `0x0004`).
148+
* - Revert if the version is unsupported.
149+
* 3. Fee handling:
150+
* - Query `s_feeManager()` on the proxy.
151+
* – Non-zero → quote the fee, approve the RewardManager,
152+
* ABI-encode the fee token address for `verify()`.
153+
* – Zero → no FeeManager; skip quoting/approval and pass `""`.
154+
* 4. Call `VerifierProxy.verify()`.
155+
* 5. Decode the verified report into the correct struct and emit the price.
156+
*
157+
* @param unverifiedReport Full payload returned.
158+
* @custom:reverts InvalidReportVersion when schema ≠ v3/v4.
159+
*/
160+
function verifyReport(bytes memory unverifiedReport) external {
161+
// ─── 1. & 2. Extract reportData and schema version ──
162+
(, bytes memory reportData) = abi.decode(
163+
unverifiedReport,
164+
(bytes32[3], bytes)
165+
);
166+
167+
uint16 reportVersion = (uint16(uint8(reportData[0])) << 8) |
168+
uint16(uint8(reportData[1]));
169+
if (reportVersion != 3 && reportVersion != 4)
170+
revert InvalidReportVersion(reportVersion);
171+
172+
// ─── 3. Fee handling ──
173+
IFeeManager feeManager = IFeeManager(
174+
address(i_verifierProxy.s_feeManager())
175+
);
176+
177+
bytes memory parameterPayload;
178+
if (address(feeManager) != address(0)) {
179+
// FeeManager exists — always quote & approve
180+
address feeToken = feeManager.i_linkAddress();
181+
182+
(Common.Asset memory fee, , ) = feeManager.getFeeAndReward(
183+
address(this),
184+
reportData,
185+
feeToken
186+
);
187+
188+
IERC20(feeToken).approve(feeManager.i_rewardManager(), fee.amount);
189+
parameterPayload = abi.encode(feeToken);
190+
} else {
191+
// No FeeManager deployed on this chain
192+
parameterPayload = bytes("");
193+
}
194+
195+
// ─── 4. Verify through the proxy ──
196+
bytes memory verified = i_verifierProxy.verify(
197+
unverifiedReport,
198+
parameterPayload
199+
);
200+
201+
// ─── 5. Decode & store price ──
202+
if (reportVersion == 3) {
203+
int192 price = abi.decode(verified, (ReportV3)).price;
204+
lastDecodedPrice = price;
205+
emit DecodedPrice(price);
206+
} else {
207+
int192 price = abi.decode(verified, (ReportV4)).price;
208+
lastDecodedPrice = price;
209+
emit DecodedPrice(price);
210+
}
211+
}
212+
213+
/**
214+
* @notice Withdraw all balance of an ERC-20 token held by this contract.
215+
* @param _beneficiary Address that receives the tokens.
216+
* @param _token ERC-20 token address.
217+
*/
218+
function withdrawToken(
219+
address _beneficiary,
220+
address _token
221+
) external onlyOwner {
222+
uint256 amount = IERC20(_token).balanceOf(address(this));
223+
if (amount == 0) revert NothingToWithdraw();
224+
IERC20(_token).safeTransfer(_beneficiary, amount);
225+
}
226+
}
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 7 additions & 0 deletions
Loading

src/components/Header/getNavigationProps.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import automationLogo from "../../assets/product-logos/automation-logo.svg"
55
import dataFeedsLogo from "../../assets/product-logos/data-feeds-logo.svg"
66
import dataStreamsLogo from "../../assets/product-logos/data-streams-logo.svg"
77
import dtaLogo from "../../assets/product-logos/dta-logo.svg"
8+
import dataLinkLogo from "../../assets/product-logos/datalink-logo.svg"
89
import chainlinkLocal from "../../assets/product-logos/chainlink-local.svg"
910
import generalLogo from "../../assets/product-logos/general-logo.svg"
1011
import nodesLogo from "../../assets/product-logos/node-logo.svg"
@@ -68,6 +69,12 @@ const desktopSubProductsNav = [
6869
icon: dtaLogo.src,
6970
col: 1,
7071
},
72+
{
73+
label: "DataLink",
74+
href: "/datalink",
75+
icon: dataLinkLogo.src,
76+
col: 1,
77+
},
7178
{
7279
label: "CCIP",
7380
href: "/ccip",
@@ -147,6 +154,12 @@ const docsSections = [
147154
icon: dataStreamsLogo.src,
148155
subProducts: getSubProducts(sidebar.dataStreams),
149156
},
157+
{
158+
label: "DataLink",
159+
href: "/datalink",
160+
icon: dataLinkLogo.src,
161+
subProducts: getSubProducts(sidebar.dataLink),
162+
},
150163
{
151164
label: "CCIP",
152165
href: "/ccip",

0 commit comments

Comments
 (0)