Skip to content

Commit 23eaa49

Browse files
authored
Merge branch 'main' into aptos-platform-addresses
2 parents 45e41e8 + 36e1bdd commit 23eaa49

File tree

3 files changed

+154
-150
lines changed

3 files changed

+154
-150
lines changed
Lines changed: 120 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// SPDX-License-Identifier: MIT
2-
32
pragma solidity 0.8.19;
43

54
import {Common} from "@chainlink/contracts/src/v0.8/llo-feeds/libraries/Common.sol";
6-
import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IRewardManager.sol";
75
import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/v0.3.0/interfaces/IVerifierFeeManager.sol";
86
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
97
import {SafeERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -13,27 +11,37 @@ using SafeERC20 for IERC20;
1311
/**
1412
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE FOR DEMONSTRATION PURPOSES.
1513
* DO NOT USE THIS CODE IN PRODUCTION.
14+
*
15+
* This contract can verify Chainlink Data Streams 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.
1628
*/
1729

18-
// Custom interfaces for IVerifierProxy and IFeeManager
30+
// ────────────────────────────────────────────────────────────────────────────
31+
// Interfaces
32+
// ────────────────────────────────────────────────────────────────────────────
33+
1934
interface IVerifierProxy {
2035
/**
21-
* @notice Verifies that the data encoded has been signed correctly by routing to the correct verifier, and bills the user if applicable.
22-
* @param payload The encoded data to be verified, including the signed report.
23-
* @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.
24-
* @return verifierResponse The encoded report from the verifier.
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.
2539
*/
2640
function verify(
2741
bytes calldata payload,
2842
bytes calldata parameterPayload
2943
) external payable returns (bytes memory verifierResponse);
3044

31-
/**
32-
* @notice Verifies multiple reports in bulk, ensuring that each is signed correctly, routes them to the appropriate verifier, and handles billing for the verification process.
33-
* @param payloads An array of encoded data to be verified, where each entry includes the signed report.
34-
* @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.
35-
* @return verifiedReports An array of encoded reports returned from the verifier.
36-
*/
3745
function verifyBulk(
3846
bytes[] calldata payloads,
3947
bytes calldata parameterPayload
@@ -44,14 +52,7 @@ interface IVerifierProxy {
4452

4553
interface IFeeManager {
4654
/**
47-
* @notice Calculates the fee and reward associated with verifying a report, including discounts for subscribers.
48-
* This function assesses the fee and reward for report verification, applying a discount for recognized subscriber addresses.
49-
* @param subscriber The address attempting to verify the report. A discount is applied if this address is recognized as a subscriber.
50-
* @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.
51-
* @param quoteAddress The payment token address used for quoting fees and rewards.
52-
* @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable.
53-
* @return reward The reward allocated to the caller for successfully verifying the report.
54-
* @return totalDiscount The total discount amount deducted from the fee for subscribers.
55+
* @return fee, reward, totalDiscount
5556
*/
5657
function getFeeAndReward(
5758
address subscriber,
@@ -66,179 +67,160 @@ interface IFeeManager {
6667
function i_rewardManager() external view returns (address);
6768
}
6869

70+
// ────────────────────────────────────────────────────────────────────────────
71+
// Contract
72+
// ────────────────────────────────────────────────────────────────────────────
73+
6974
/**
7075
* @dev This contract implements functionality to verify Data Streams reports from
71-
* the Streams Direct API or WebSocket connection, with payment in LINK tokens.
76+
* the Data Streams API, with payment in LINK tokens.
7277
*/
7378
contract ClientReportsVerifier {
74-
error NothingToWithdraw(); // Thrown when a withdrawal attempt is made but the contract holds no tokens of the specified type.
75-
error NotOwner(address caller); // Thrown when a caller tries to execute a function that is restricted to the contract's owner.
76-
error InvalidReportVersion(uint16 version); // Thrown when an unsupported report version is provided to verifyReport.
79+
// ----------------- Errors -----------------
80+
error NothingToWithdraw();
81+
error NotOwner(address caller);
82+
error InvalidReportVersion(uint16 version);
7783

84+
// ----------------- Report schemas -----------------
85+
// More info: https://docs.chain.link/data-streams/reference/report-schema
7886
/**
79-
* @dev Represents a data report from a Data Streams stream for v3 schema (crypto streams).
80-
* The `price`, `bid`, and `ask` values are carried to either 8 or 18 decimal places, depending on the stream.
81-
* For more information, see https://docs.chain.link/data-streams/crypto-streams and https://docs.chain.link/data-streams/reference/report-schema
87+
* @dev Data Streams report schema v3 (crypto streams).
88+
* Prices, bids and asks use 8 or 18 decimals depending on the stream.
8289
*/
8390
struct ReportV3 {
84-
bytes32 feedId; // The stream ID the report has data for.
85-
uint32 validFromTimestamp; // Earliest timestamp for which price is applicable.
86-
uint32 observationsTimestamp; // Latest timestamp for which price is applicable.
87-
uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH).
88-
uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK.
89-
uint32 expiresAt; // Latest timestamp where the report can be verified onchain.
90-
int192 price; // DON consensus median price (8 or 18 decimals).
91-
int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation (8 or 18 decimals).
92-
int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation (8 or 18 decimals).
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;
93100
}
94101

95102
/**
96-
* @dev Represents a data report from a Data Streams stream for v4 schema (RWA stream).
97-
* The `price` value is carried to either 8 or 18 decimal places, depending on the stream.
98-
* The `marketStatus` indicates whether the market is currently open. Possible values: `0` (`Unknown`), `1` (`Closed`), `2` (`Open`).
99-
* For more information, see https://docs.chain.link/data-streams/rwa-streams and https://docs.chain.link/data-streams/reference/report-schema-v4
103+
* @dev Data Streams report schema v4 (RWA streams).
100104
*/
101105
struct ReportV4 {
102-
bytes32 feedId; // The stream ID the report has data for.
103-
uint32 validFromTimestamp; // Earliest timestamp for which price is applicable.
104-
uint32 observationsTimestamp; // Latest timestamp for which price is applicable.
105-
uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH).
106-
uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK.
107-
uint32 expiresAt; // Latest timestamp where the report can be verified onchain.
108-
int192 price; // DON consensus median benchmark price (8 or 18 decimals).
109-
uint32 marketStatus; // The DON's consensus on whether the market is currently open.
106+
bytes32 feedId;
107+
uint32 validFromTimestamp;
108+
uint32 observationsTimestamp;
109+
uint192 nativeFee;
110+
uint192 linkFee;
111+
uint32 expiresAt;
112+
int192 price;
113+
uint32 marketStatus;
110114
}
111115

112-
IVerifierProxy public s_verifierProxy; // The VerifierProxy contract used for report verification.
116+
// ----------------- Storage -----------------
117+
IVerifierProxy public immutable i_verifierProxy;
118+
address private immutable i_owner;
113119

114-
address private s_owner; // The owner of the contract.
115-
int192 public lastDecodedPrice; // Stores the last decoded price from a verified report.
120+
int192 public lastDecodedPrice;
116121

117-
event DecodedPrice(int192 price); // Event emitted when a report is successfully verified and decoded.
122+
// ----------------- Events -----------------
123+
event DecodedPrice(int192 price);
118124

125+
// ----------------- Constructor / modifier -----------------
119126
/**
120-
* @param _verifierProxy The address of the VerifierProxy contract.
121-
* You can find these addresses on https://docs.chain.link/data-streams/crypto-streams.
127+
* @param _verifierProxy Address of the VerifierProxy on the target network.
128+
* Addresses: https://docs.chain.link/data-streams/crypto-streams
122129
*/
123130
constructor(address _verifierProxy) {
124-
s_owner = msg.sender;
125-
s_verifierProxy = IVerifierProxy(_verifierProxy);
131+
i_owner = msg.sender;
132+
i_verifierProxy = IVerifierProxy(_verifierProxy);
126133
}
127134

128-
/// @notice Checks if the caller is the owner of the contract.
129135
modifier onlyOwner() {
130-
if (msg.sender != s_owner) revert NotOwner(msg.sender);
136+
if (msg.sender != i_owner) revert NotOwner(msg.sender);
131137
_;
132138
}
133139

140+
// ----------------- Public API -----------------
141+
134142
/**
135-
* @notice Verifies an unverified data report and processes its contents, supporting both v3 and v4 report schemas.
136-
* @dev Performs the following steps:
137-
* - Decodes the unverified report to extract the report data.
138-
* - Extracts the report version by reading the first two bytes of the report data.
139-
* - The first two bytes correspond to the schema version encoded in the stream ID.
140-
* - Schema version `0x0003` corresponds to report version 3 (for Crypto assets).
141-
* - Schema version `0x0004` corresponds to report version 4 (for Real World Assets).
142-
* - Validates that the report version is either 3 or 4; reverts with `InvalidReportVersion` otherwise.
143-
* - Retrieves the fee manager and reward manager contracts.
144-
* - Calculates the fee required for report verification using the fee manager.
145-
* - Approves the reward manager to spend the calculated fee amount.
146-
* - Verifies the report via the VerifierProxy contract.
147-
* - Decodes the verified report data into the appropriate report struct (`ReportV3` or `ReportV4`) based on the report version.
148-
* - Emits a `DecodedPrice` event with the price extracted from the verified report.
149-
* - Updates the `lastDecodedPrice` state variable with the price from the verified report.
150-
* @param unverifiedReport The encoded report data to be verified, including the signed report and metadata.
151-
* @custom:reverts InvalidReportVersion(uint8 version) Thrown when an unsupported report version is provided.
143+
* @notice Verify a Data Streams 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 by Streams Direct.
158+
* @custom:reverts InvalidReportVersion when schema ≠ v3/v4.
152159
*/
153160
function verifyReport(bytes memory unverifiedReport) external {
154-
// Retrieve fee manager and reward manager
155-
IFeeManager feeManager = IFeeManager(
156-
address(s_verifierProxy.s_feeManager())
157-
);
158-
159-
IRewardManager rewardManager = IRewardManager(
160-
address(feeManager.i_rewardManager())
161-
);
162-
163-
// Decode unverified report to extract report data
161+
// ─── 1. & 2. Extract reportData and schema version ──
164162
(, bytes memory reportData) = abi.decode(
165163
unverifiedReport,
166164
(bytes32[3], bytes)
167165
);
168166

169-
// Extract report version from reportData
170167
uint16 reportVersion = (uint16(uint8(reportData[0])) << 8) |
171168
uint16(uint8(reportData[1]));
169+
if (reportVersion != 3 && reportVersion != 4)
170+
revert InvalidReportVersion(reportVersion);
172171

173-
// Validate report version
174-
if (reportVersion != 3 && reportVersion != 4) {
175-
revert InvalidReportVersion(uint8(reportVersion));
176-
}
172+
// ─── 3. Fee handling ──
173+
IFeeManager feeManager = IFeeManager(
174+
address(i_verifierProxy.s_feeManager())
175+
);
177176

178-
// Set the fee token address (LINK in this case)
179-
address feeTokenAddress = feeManager.i_linkAddress();
177+
bytes memory parameterPayload;
178+
if (address(feeManager) != address(0)) {
179+
// FeeManager exists — always quote & approve
180+
address feeToken = feeManager.i_linkAddress();
180181

181-
// Calculate the fee required for report verification
182-
(Common.Asset memory fee, , ) = feeManager.getFeeAndReward(
183-
address(this),
184-
reportData,
185-
feeTokenAddress
186-
);
182+
(Common.Asset memory fee, , ) = feeManager.getFeeAndReward(
183+
address(this),
184+
reportData,
185+
feeToken
186+
);
187187

188-
// Approve rewardManager to spend this contract's balance in fees
189-
IERC20(feeTokenAddress).approve(address(rewardManager), fee.amount);
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+
}
190194

191-
// Verify the report through the VerifierProxy
192-
bytes memory verifiedReportData = s_verifierProxy.verify(
195+
// ─── 4. Verify through the proxy ──
196+
bytes memory verified = i_verifierProxy.verify(
193197
unverifiedReport,
194-
abi.encode(feeTokenAddress)
198+
parameterPayload
195199
);
196200

197-
// Decode verified report data into the appropriate Report struct based on reportVersion
201+
// ─── 5. Decode & store price ──
198202
if (reportVersion == 3) {
199-
// v3 report schema
200-
ReportV3 memory verifiedReport = abi.decode(
201-
verifiedReportData,
202-
(ReportV3)
203-
);
204-
205-
// Log price from the verified report
206-
emit DecodedPrice(verifiedReport.price);
207-
208-
// Store the price from the report
209-
lastDecodedPrice = verifiedReport.price;
210-
} else if (reportVersion == 4) {
211-
// v4 report schema
212-
ReportV4 memory verifiedReport = abi.decode(
213-
verifiedReportData,
214-
(ReportV4)
215-
);
216-
217-
// Log price from the verified report
218-
emit DecodedPrice(verifiedReport.price);
219-
220-
// Store the price from the report
221-
lastDecodedPrice = verifiedReport.price;
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);
222210
}
223211
}
224212

225213
/**
226-
* @notice Withdraws all tokens of a specific ERC20 token type to a beneficiary address.
227-
* @dev Utilizes SafeERC20's safeTransfer for secure token transfer. Reverts if the contract's balance of the specified token is zero.
228-
* @param _beneficiary Address to which the tokens will be sent. Must not be the zero address.
229-
* @param _token Address of the ERC20 token to be withdrawn. Must be a valid ERC20 token contract.
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.
230217
*/
231218
function withdrawToken(
232219
address _beneficiary,
233220
address _token
234-
) public onlyOwner {
235-
// Retrieve the balance of this contract for the specified token
221+
) external onlyOwner {
236222
uint256 amount = IERC20(_token).balanceOf(address(this));
237-
238-
// Revert if there is nothing to withdraw
239223
if (amount == 0) revert NothingToWithdraw();
240-
241-
// Transfer the tokens to the beneficiary
242224
IERC20(_token).safeTransfer(_beneficiary, amount);
243225
}
244226
}

0 commit comments

Comments
 (0)