Skip to content

Commit 23a446c

Browse files
committed
feat: Add ReportDataV7 schema
1 parent 3069bff commit 23a446c

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

rust/crates/report/src/report.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod v3;
66
pub mod v4;
77
pub mod v5;
88
pub mod v6;
9+
pub mod v7;
910
pub mod v8;
1011
pub mod v9;
1112
pub mod v10;
@@ -122,7 +123,7 @@ pub fn decode_full_report(payload: &[u8]) -> Result<(Vec<[u8; 32]>, Vec<u8>), Re
122123
#[cfg(test)]
123124
mod tests {
124125
use super::*;
125-
use crate::report::{v1::ReportDataV1, v2::ReportDataV2, v3::ReportDataV3, v4::ReportDataV4, v5::ReportDataV5, v6::ReportDataV6, v8::ReportDataV8, v9::ReportDataV9, v10::ReportDataV10};
126+
use crate::report::{v1::ReportDataV1, v2::ReportDataV2, v3::ReportDataV3, v4::ReportDataV4, v5::ReportDataV5, v6::ReportDataV6, v7::ReportDataV7, v8::ReportDataV8, v9::ReportDataV9, v10::ReportDataV10};
126127
use num_bigint::BigInt;
127128

128129
const V1_FEED_ID: ID = ID([
@@ -149,6 +150,10 @@ mod tests {
149150
00, 06, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
150151
163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
151152
]);
153+
const V7_FEED_ID: ID = ID([
154+
00, 07, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
155+
163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
156+
]);
152157
const V8_FEED_ID: ID = ID([
153158
00, 08, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
154159
163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
@@ -269,6 +274,20 @@ mod tests {
269274
report_data
270275
}
271276

277+
pub fn generate_mock_report_data_v7() -> ReportDataV7 {
278+
let report_data = ReportDataV7 {
279+
feed_id: V7_FEED_ID,
280+
valid_from_timestamp: MOCK_TIMESTAMP,
281+
observations_timestamp: MOCK_TIMESTAMP,
282+
native_fee: BigInt::from(MOCK_FEE),
283+
link_fee: BigInt::from(MOCK_FEE),
284+
expires_at: MOCK_TIMESTAMP + 100,
285+
exchange_rate: BigInt::from(MOCK_PRICE),
286+
};
287+
288+
report_data
289+
}
290+
272291
pub fn generate_mock_report_data_v8() -> ReportDataV8 {
273292
let report_data = ReportDataV8 {
274293
feed_id: V8_FEED_ID,
@@ -551,6 +570,35 @@ mod tests {
551570
assert_eq!(decoded_report.feed_id, V6_FEED_ID);
552571
}
553572

573+
#[test]
574+
fn test_decode_report_v7() {
575+
let report_data = generate_mock_report_data_v7();
576+
let encoded_report_data = report_data.abi_encode().unwrap();
577+
578+
let report = generate_mock_report(&encoded_report_data);
579+
580+
let (_report_context, report_blob) = decode_full_report(&report).unwrap();
581+
582+
let expected_report_blob = vec![
583+
"00076b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472",
584+
"0000000000000000000000000000000000000000000000000000000066741d8c",
585+
"0000000000000000000000000000000000000000000000000000000066741d8c",
586+
"000000000000000000000000000000000000000000000000000000000000000a",
587+
"000000000000000000000000000000000000000000000000000000000000000a",
588+
"0000000000000000000000000000000000000000000000000000000066741df0",
589+
"0000000000000000000000000000000000000000000000000000000000000064", // Exchange Rate: 100
590+
];
591+
592+
assert_eq!(
593+
report_blob,
594+
bytes(&format!("0x{}", expected_report_blob.join("")))
595+
);
596+
597+
let decoded_report = ReportDataV7::decode(&report_blob).unwrap();
598+
599+
assert_eq!(decoded_report.feed_id, V7_FEED_ID);
600+
}
601+
554602
#[test]
555603
fn test_decode_report_v8() {
556604
let report_data = generate_mock_report_data_v8();
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use crate::feed_id::ID;
2+
use crate::report::base::{ReportBase, ReportError};
3+
4+
use num_bigint::BigInt;
5+
6+
/// Represents a Report Data V7 Schema.
7+
///
8+
/// # Parameters
9+
/// - `feed_id`: The feed ID the report has data for.
10+
/// - `valid_from_timestamp`: Earliest timestamp for which price is applicable.
11+
/// - `observations_timestamp`: Latest timestamp for which price is applicable.
12+
/// - `native_fee`: Base cost to validate a transaction using the report, denominated in the chain's native token (e.g., WETH/ETH).
13+
/// - `link_fee`: Base cost to validate a transaction using the report, denominated in LINK.
14+
/// - `expires_at`: Latest timestamp where the report can be verified onchain.
15+
/// - `exchange_rate`: The exchange rate.
16+
///
17+
/// # Solidity Equivalent
18+
/// ```solidity
19+
/// struct ReportDataV7 {
20+
/// bytes32 feedId;
21+
/// uint32 validFromTimestamp;
22+
/// uint32 observationsTimestamp;
23+
/// uint192 nativeFee;
24+
/// uint192 linkFee;
25+
/// uint32 expiresAt;
26+
/// int192 exchangeRate;
27+
/// }
28+
/// ```
29+
#[derive(Debug)]
30+
pub struct ReportDataV7 {
31+
pub feed_id: ID,
32+
pub valid_from_timestamp: u32,
33+
pub observations_timestamp: u32,
34+
pub native_fee: BigInt,
35+
pub link_fee: BigInt,
36+
pub expires_at: u32,
37+
pub exchange_rate: BigInt,
38+
}
39+
40+
impl ReportDataV7 {
41+
/// Decodes an ABI-encoded `ReportDataV7` from bytes.
42+
///
43+
/// # Parameters
44+
///
45+
/// - `data`: The encoded report data.
46+
///
47+
/// # Returns
48+
///
49+
/// The decoded `ReportDataV7`.
50+
///
51+
/// # Errors
52+
///
53+
/// Returns a `ReportError` if the data is too short or if the data is invalid.
54+
pub fn decode(data: &[u8]) -> Result<Self, ReportError> {
55+
if data.len() < 7 * ReportBase::WORD_SIZE {
56+
return Err(ReportError::DataTooShort("ReportDataV7"));
57+
}
58+
59+
let feed_id = ID(data[..ReportBase::WORD_SIZE]
60+
.try_into()
61+
.map_err(|_| ReportError::InvalidLength("feed_id (bytes32)"))?);
62+
63+
let valid_from_timestamp = ReportBase::read_uint32(data, ReportBase::WORD_SIZE)?;
64+
let observations_timestamp = ReportBase::read_uint32(data, 2 * ReportBase::WORD_SIZE)?;
65+
let native_fee = ReportBase::read_uint192(data, 3 * ReportBase::WORD_SIZE)?;
66+
let link_fee = ReportBase::read_uint192(data, 4 * ReportBase::WORD_SIZE)?;
67+
let expires_at = ReportBase::read_uint32(data, 5 * ReportBase::WORD_SIZE)?;
68+
let exchange_rate = ReportBase::read_int192(data, 6 * ReportBase::WORD_SIZE)?;
69+
70+
Ok(Self {
71+
feed_id,
72+
valid_from_timestamp,
73+
observations_timestamp,
74+
native_fee,
75+
link_fee,
76+
expires_at,
77+
exchange_rate,
78+
})
79+
}
80+
81+
/// Encodes the `ReportDataV7` into an ABI-encoded byte array.
82+
///
83+
/// # Returns
84+
///
85+
/// The ABI-encoded report data.
86+
///
87+
/// # Errors
88+
///
89+
/// Returns a `ReportError` if the data is invalid.
90+
pub fn abi_encode(&self) -> Result<Vec<u8>, ReportError> {
91+
let mut buffer = Vec::with_capacity(7 * ReportBase::WORD_SIZE);
92+
93+
buffer.extend_from_slice(&self.feed_id.0);
94+
buffer.extend_from_slice(&ReportBase::encode_uint32(self.valid_from_timestamp)?);
95+
buffer.extend_from_slice(&ReportBase::encode_uint32(self.observations_timestamp)?);
96+
buffer.extend_from_slice(&ReportBase::encode_uint192(&self.native_fee)?);
97+
buffer.extend_from_slice(&ReportBase::encode_uint192(&self.link_fee)?);
98+
buffer.extend_from_slice(&ReportBase::encode_uint32(self.expires_at)?);
99+
buffer.extend_from_slice(&ReportBase::encode_int192(&self.exchange_rate)?);
100+
101+
Ok(buffer)
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use super::*;
108+
use crate::report::tests::{
109+
generate_mock_report_data_v7, MOCK_FEE, MOCK_PRICE, MOCK_TIMESTAMP,
110+
};
111+
112+
const V7_FEED_ID_STR: &str =
113+
"0x00076b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472";
114+
115+
#[test]
116+
fn test_decode_report_data_v7() {
117+
let report_data = generate_mock_report_data_v7();
118+
let encoded = report_data.abi_encode().unwrap();
119+
let decoded = ReportDataV7::decode(&encoded).unwrap();
120+
121+
let expected_feed_id = ID::from_hex_str(V7_FEED_ID_STR).unwrap();
122+
let expected_timestamp: u32 = MOCK_TIMESTAMP;
123+
let expected_fee = BigInt::from(MOCK_FEE);
124+
let expected_exchange_rate = BigInt::from(MOCK_PRICE);
125+
126+
assert_eq!(decoded.feed_id, expected_feed_id);
127+
assert_eq!(decoded.valid_from_timestamp, expected_timestamp);
128+
assert_eq!(decoded.observations_timestamp, expected_timestamp);
129+
assert_eq!(decoded.native_fee, expected_fee);
130+
assert_eq!(decoded.link_fee, expected_fee);
131+
assert_eq!(decoded.expires_at, expected_timestamp + 100);
132+
assert_eq!(decoded.exchange_rate, expected_exchange_rate);
133+
}
134+
}

0 commit comments

Comments
 (0)