Skip to content

Commit 3069bff

Browse files
committed
feat: Add ReportDataV6 schema
1 parent 0963b7f commit 3069bff

File tree

2 files changed

+219
-1
lines changed

2 files changed

+219
-1
lines changed

rust/crates/report/src/report.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod v2;
55
pub mod v3;
66
pub mod v4;
77
pub mod v5;
8+
pub mod v6;
89
pub mod v8;
910
pub mod v9;
1011
pub mod v10;
@@ -121,7 +122,7 @@ pub fn decode_full_report(payload: &[u8]) -> Result<(Vec<[u8; 32]>, Vec<u8>), Re
121122
#[cfg(test)]
122123
mod tests {
123124
use super::*;
124-
use crate::report::{v1::ReportDataV1, v2::ReportDataV2, v3::ReportDataV3, v4::ReportDataV4, v5::ReportDataV5, v8::ReportDataV8, v9::ReportDataV9, v10::ReportDataV10};
125+
use crate::report::{v1::ReportDataV1, v2::ReportDataV2, v3::ReportDataV3, v4::ReportDataV4, v5::ReportDataV5, v6::ReportDataV6, v8::ReportDataV8, v9::ReportDataV9, v10::ReportDataV10};
125126
use num_bigint::BigInt;
126127

127128
const V1_FEED_ID: ID = ID([
@@ -144,6 +145,10 @@ mod tests {
144145
00, 05, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
145146
163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
146147
]);
148+
const V6_FEED_ID: ID = ID([
149+
00, 06, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
150+
163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
151+
]);
147152
const V8_FEED_ID: ID = ID([
148153
00, 08, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58,
149154
163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114,
@@ -246,6 +251,24 @@ mod tests {
246251
report_data
247252
}
248253

254+
pub fn generate_mock_report_data_v6() -> ReportDataV6 {
255+
let report_data = ReportDataV6 {
256+
feed_id: V6_FEED_ID,
257+
valid_from_timestamp: MOCK_TIMESTAMP,
258+
observations_timestamp: MOCK_TIMESTAMP,
259+
native_fee: BigInt::from(MOCK_FEE),
260+
link_fee: BigInt::from(MOCK_FEE),
261+
expires_at: MOCK_TIMESTAMP + 100,
262+
price: BigInt::from(MOCK_PRICE),
263+
price2: BigInt::from(MOCK_PRICE + 10),
264+
price3: BigInt::from(MOCK_PRICE + 20),
265+
price4: BigInt::from(MOCK_PRICE + 30),
266+
price5: BigInt::from(MOCK_PRICE + 40),
267+
};
268+
269+
report_data
270+
}
271+
249272
pub fn generate_mock_report_data_v8() -> ReportDataV8 {
250273
let report_data = ReportDataV8 {
251274
feed_id: V8_FEED_ID,
@@ -495,6 +518,39 @@ mod tests {
495518
assert_eq!(decoded_report.feed_id, V5_FEED_ID);
496519
}
497520

521+
#[test]
522+
fn test_decode_report_v6() {
523+
let report_data = generate_mock_report_data_v6();
524+
let encoded_report_data = report_data.abi_encode().unwrap();
525+
526+
let report = generate_mock_report(&encoded_report_data);
527+
528+
let (_report_context, report_blob) = decode_full_report(&report).unwrap();
529+
530+
let expected_report_blob = vec![
531+
"00066b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472",
532+
"0000000000000000000000000000000000000000000000000000000066741d8c",
533+
"0000000000000000000000000000000000000000000000000000000066741d8c",
534+
"000000000000000000000000000000000000000000000000000000000000000a",
535+
"000000000000000000000000000000000000000000000000000000000000000a",
536+
"0000000000000000000000000000000000000000000000000000000066741df0",
537+
"0000000000000000000000000000000000000000000000000000000000000064", // Price: 100
538+
"000000000000000000000000000000000000000000000000000000000000006e", // Price2: 110
539+
"0000000000000000000000000000000000000000000000000000000000000078", // Price3: 120
540+
"0000000000000000000000000000000000000000000000000000000000000082", // Price4: 130
541+
"000000000000000000000000000000000000000000000000000000000000008c", // Price5: 140
542+
];
543+
544+
assert_eq!(
545+
report_blob,
546+
bytes(&format!("0x{}", expected_report_blob.join("")))
547+
);
548+
549+
let decoded_report = ReportDataV6::decode(&report_blob).unwrap();
550+
551+
assert_eq!(decoded_report.feed_id, V6_FEED_ID);
552+
}
553+
498554
#[test]
499555
fn test_decode_report_v8() {
500556
let report_data = generate_mock_report_data_v8();
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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 V6 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+
/// - `price`: The primary price value.
16+
/// - `price2`: The second price value.
17+
/// - `price3`: The third price value.
18+
/// - `price4`: The fourth price value.
19+
/// - `price5`: The fifth price value.
20+
///
21+
/// # Solidity Equivalent
22+
/// ```solidity
23+
/// struct ReportDataV6 {
24+
/// bytes32 feedId;
25+
/// uint32 validFromTimestamp;
26+
/// uint32 observationsTimestamp;
27+
/// uint192 nativeFee;
28+
/// uint192 linkFee;
29+
/// uint32 expiresAt;
30+
/// int192 price;
31+
/// int192 price2;
32+
/// int192 price3;
33+
/// int192 price4;
34+
/// int192 price5;
35+
/// }
36+
/// ```
37+
#[derive(Debug)]
38+
pub struct ReportDataV6 {
39+
pub feed_id: ID,
40+
pub valid_from_timestamp: u32,
41+
pub observations_timestamp: u32,
42+
pub native_fee: BigInt,
43+
pub link_fee: BigInt,
44+
pub expires_at: u32,
45+
pub price: BigInt,
46+
pub price2: BigInt,
47+
pub price3: BigInt,
48+
pub price4: BigInt,
49+
pub price5: BigInt,
50+
}
51+
52+
impl ReportDataV6 {
53+
/// Decodes an ABI-encoded `ReportDataV6` from bytes.
54+
///
55+
/// # Parameters
56+
///
57+
/// - `data`: The encoded report data.
58+
///
59+
/// # Returns
60+
///
61+
/// The decoded `ReportDataV6`.
62+
///
63+
/// # Errors
64+
///
65+
/// Returns a `ReportError` if the data is too short or if the data is invalid.
66+
pub fn decode(data: &[u8]) -> Result<Self, ReportError> {
67+
if data.len() < 11 * ReportBase::WORD_SIZE {
68+
return Err(ReportError::DataTooShort("ReportDataV6"));
69+
}
70+
71+
let feed_id = ID(data[..ReportBase::WORD_SIZE]
72+
.try_into()
73+
.map_err(|_| ReportError::InvalidLength("feed_id (bytes32)"))?);
74+
75+
let valid_from_timestamp = ReportBase::read_uint32(data, ReportBase::WORD_SIZE)?;
76+
let observations_timestamp = ReportBase::read_uint32(data, 2 * ReportBase::WORD_SIZE)?;
77+
let native_fee = ReportBase::read_uint192(data, 3 * ReportBase::WORD_SIZE)?;
78+
let link_fee = ReportBase::read_uint192(data, 4 * ReportBase::WORD_SIZE)?;
79+
let expires_at = ReportBase::read_uint32(data, 5 * ReportBase::WORD_SIZE)?;
80+
let price = ReportBase::read_int192(data, 6 * ReportBase::WORD_SIZE)?;
81+
let price2 = ReportBase::read_int192(data, 7 * ReportBase::WORD_SIZE)?;
82+
let price3 = ReportBase::read_int192(data, 8 * ReportBase::WORD_SIZE)?;
83+
let price4 = ReportBase::read_int192(data, 9 * ReportBase::WORD_SIZE)?;
84+
let price5 = ReportBase::read_int192(data, 10 * ReportBase::WORD_SIZE)?;
85+
86+
Ok(Self {
87+
feed_id,
88+
valid_from_timestamp,
89+
observations_timestamp,
90+
native_fee,
91+
link_fee,
92+
expires_at,
93+
price,
94+
price2,
95+
price3,
96+
price4,
97+
price5,
98+
})
99+
}
100+
101+
/// Encodes the `ReportDataV6` into an ABI-encoded byte array.
102+
///
103+
/// # Returns
104+
///
105+
/// The ABI-encoded report data.
106+
///
107+
/// # Errors
108+
///
109+
/// Returns a `ReportError` if the data is invalid.
110+
pub fn abi_encode(&self) -> Result<Vec<u8>, ReportError> {
111+
let mut buffer = Vec::with_capacity(11 * ReportBase::WORD_SIZE);
112+
113+
buffer.extend_from_slice(&self.feed_id.0);
114+
buffer.extend_from_slice(&ReportBase::encode_uint32(self.valid_from_timestamp)?);
115+
buffer.extend_from_slice(&ReportBase::encode_uint32(self.observations_timestamp)?);
116+
buffer.extend_from_slice(&ReportBase::encode_uint192(&self.native_fee)?);
117+
buffer.extend_from_slice(&ReportBase::encode_uint192(&self.link_fee)?);
118+
buffer.extend_from_slice(&ReportBase::encode_uint32(self.expires_at)?);
119+
buffer.extend_from_slice(&ReportBase::encode_int192(&self.price)?);
120+
buffer.extend_from_slice(&ReportBase::encode_int192(&self.price2)?);
121+
buffer.extend_from_slice(&ReportBase::encode_int192(&self.price3)?);
122+
buffer.extend_from_slice(&ReportBase::encode_int192(&self.price4)?);
123+
buffer.extend_from_slice(&ReportBase::encode_int192(&self.price5)?);
124+
125+
Ok(buffer)
126+
}
127+
}
128+
129+
#[cfg(test)]
130+
mod tests {
131+
use super::*;
132+
use crate::report::tests::{
133+
generate_mock_report_data_v6, MOCK_FEE, MOCK_PRICE, MOCK_TIMESTAMP,
134+
};
135+
136+
const V6_FEED_ID_STR: &str =
137+
"0x00066b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472";
138+
139+
#[test]
140+
fn test_decode_report_data_v6() {
141+
let report_data = generate_mock_report_data_v6();
142+
let encoded = report_data.abi_encode().unwrap();
143+
let decoded = ReportDataV6::decode(&encoded).unwrap();
144+
145+
let expected_feed_id = ID::from_hex_str(V6_FEED_ID_STR).unwrap();
146+
let expected_timestamp: u32 = MOCK_TIMESTAMP;
147+
let expected_fee = BigInt::from(MOCK_FEE);
148+
let expected_price = BigInt::from(MOCK_PRICE);
149+
150+
assert_eq!(decoded.feed_id, expected_feed_id);
151+
assert_eq!(decoded.valid_from_timestamp, expected_timestamp);
152+
assert_eq!(decoded.observations_timestamp, expected_timestamp);
153+
assert_eq!(decoded.native_fee, expected_fee);
154+
assert_eq!(decoded.link_fee, expected_fee);
155+
assert_eq!(decoded.expires_at, expected_timestamp + 100);
156+
assert_eq!(decoded.price, expected_price);
157+
assert_eq!(decoded.price2, BigInt::from(MOCK_PRICE + 10));
158+
assert_eq!(decoded.price3, BigInt::from(MOCK_PRICE + 20));
159+
assert_eq!(decoded.price4, BigInt::from(MOCK_PRICE + 30));
160+
assert_eq!(decoded.price5, BigInt::from(MOCK_PRICE + 40));
161+
}
162+
}

0 commit comments

Comments
 (0)