1use crate::{SUPER_ROOT_VERSION, SuperRootError, SuperRootResult};
6use alloc::vec::Vec;
7use alloy_eips::BlockNumHash;
8use alloy_primitives::{B256, Bytes, U256, keccak256};
9use alloy_rlp::{Buf, BufMut};
10
11#[derive(Debug, Clone, Eq, PartialEq)]
13#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct SuperRoot {
16 pub timestamp: u64,
18 pub output_roots: Vec<OutputRootWithChain>,
20}
21
22impl SuperRoot {
23 pub fn new(timestamp: u64, mut output_roots: Vec<OutputRootWithChain>) -> Self {
25 output_roots.sort_by_key(|r| r.chain_id);
27 Self { timestamp, output_roots }
28 }
29
30 pub fn decode(buf: &mut &[u8]) -> SuperRootResult<Self> {
32 if buf.is_empty() {
33 return Err(SuperRootError::UnexpectedLength);
34 }
35
36 let version = buf[0];
37 if version != SUPER_ROOT_VERSION {
38 return Err(SuperRootError::InvalidVersionByte);
39 }
40 buf.advance(1);
41
42 if buf.len() < 8 {
43 return Err(SuperRootError::UnexpectedLength);
44 }
45 let timestamp = u64::from_be_bytes(buf[0..8].try_into()?);
46 buf.advance(8);
47
48 let mut output_roots = Vec::new();
49 while !buf.is_empty() {
50 if buf.len() < 64 {
51 return Err(SuperRootError::UnexpectedLength);
52 }
53
54 let chain_id = U256::from_be_bytes::<32>(buf[0..32].try_into()?);
55 buf.advance(32);
56 let output_root = B256::from_slice(&buf[0..32]);
57 buf.advance(32);
58 output_roots.push(OutputRootWithChain::new(chain_id.to(), output_root));
59 }
60
61 Ok(Self { timestamp, output_roots })
62 }
63
64 pub fn encode(&self, out: &mut dyn BufMut) {
66 out.put_u8(SUPER_ROOT_VERSION);
67
68 out.put_u64(self.timestamp);
69 for output_root in &self.output_roots {
70 out.put_slice(U256::from(output_root.chain_id).to_be_bytes::<32>().as_slice());
71 out.put_slice(output_root.output_root.as_slice());
72 }
73 }
74
75 pub fn encoded_length(&self) -> usize {
77 1 + 8 + 64 * self.output_roots.len()
78 }
79
80 pub fn hash(&self) -> B256 {
82 let mut rlp_buf = Vec::with_capacity(self.encoded_length());
83 self.encode(&mut rlp_buf);
84 keccak256(&rlp_buf)
85 }
86}
87
88#[derive(Debug, Clone, Eq, PartialEq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
92pub struct ChainRootInfo {
93 #[cfg_attr(feature = "serde", serde(rename = "chainID"))]
95 pub chain_id: u64,
96 pub canonical: B256,
98 pub pending: Bytes,
106}
107
108#[derive(Debug, Clone, Eq, PartialEq)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
112pub struct SuperRootOutput {
113 pub cross_safe_derived_from: BlockNumHash,
115 pub timestamp: u64,
117 pub super_root: B256,
119 pub version: u8,
121 pub chains: Vec<ChainRootInfo>,
124}
125
126#[derive(Debug, Clone, Eq, PartialEq, Hash)]
128#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130pub struct OutputRootWithChain {
131 pub chain_id: u64,
133 pub output_root: B256,
135}
136
137impl OutputRootWithChain {
138 pub const fn new(chain_id: u64, output_root: B256) -> Self {
140 Self { chain_id, output_root }
141 }
142}
143
144#[cfg(test)]
145mod test {
146 use crate::{SUPER_ROOT_VERSION, errors::SuperRootError};
147
148 use super::{OutputRootWithChain, SuperRoot};
149 use alloy_primitives::{B256, b256};
150
151 #[test]
152 fn test_super_root_sorts_outputs() {
153 let super_root = SuperRoot::new(
154 10,
155 vec![
156 (OutputRootWithChain::new(3, B256::default())),
157 (OutputRootWithChain::new(2, B256::default())),
158 (OutputRootWithChain::new(1, B256::default())),
159 ],
160 );
161
162 assert!(super_root.output_roots.windows(2).all(|w| w[0].chain_id <= w[1].chain_id));
163 }
164
165 #[test]
166 fn test_super_root_empty_buf() {
167 let buf: Vec<u8> = Vec::new();
168 assert!(matches!(
169 SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
170 SuperRootError::UnexpectedLength
171 ));
172 }
173
174 #[test]
175 fn test_super_root_invalid_version() {
176 let buf = vec![0xFF];
177 assert!(matches!(
178 SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
179 SuperRootError::InvalidVersionByte
180 ));
181 }
182
183 #[test]
184 fn test_super_root_invalid_length_at_timestamp() {
185 let buf = vec![SUPER_ROOT_VERSION, 0x00];
186 assert!(matches!(
187 SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
188 SuperRootError::UnexpectedLength
189 ));
190 }
191
192 #[test]
193 fn test_super_root_invalid_length_malformed_output_roots() {
194 let buf = [&[SUPER_ROOT_VERSION], 64u64.to_be_bytes().as_ref(), &[0xbe, 0xef]].concat();
195 assert!(matches!(
196 SuperRoot::decode(&mut buf.as_slice()).unwrap_err(),
197 SuperRootError::UnexpectedLength
198 ));
199 }
200
201 #[test]
202 fn test_static_hash_super_root() {
203 const EXPECTED: B256 =
204 b256!("0980033cbf4337f614a2401ab7efbfdc66ab647812f1c98d891d92ddfb376541");
205
206 let super_root = SuperRoot::new(
207 10,
208 vec![
209 (OutputRootWithChain::new(1, B256::default())),
210 (OutputRootWithChain::new(2, B256::default())),
211 ],
212 );
213 assert_eq!(super_root.hash(), EXPECTED);
214 }
215
216 #[test]
217 fn test_static_super_root_roundtrip() {
218 let super_root = SuperRoot::new(
219 10,
220 vec![
221 (OutputRootWithChain::new(1, B256::default())),
222 (OutputRootWithChain::new(2, B256::default())),
223 ],
224 );
225
226 let mut rlp_buf = Vec::with_capacity(super_root.encoded_length());
227 super_root.encode(&mut rlp_buf);
228 assert_eq!(super_root, SuperRoot::decode(&mut rlp_buf.as_slice()).unwrap());
229 }
230
231 #[test]
232 fn test_arbitrary_super_root_roundtrip() {
233 use arbitrary::Arbitrary;
234 use rand::Rng;
235
236 let mut bytes = [0u8; 1024];
237 rand::rng().fill(bytes.as_mut_slice());
238 let super_root = SuperRoot::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
239
240 let mut rlp_buf = Vec::with_capacity(super_root.encoded_length());
241 super_root.encode(&mut rlp_buf);
242 assert_eq!(super_root, SuperRoot::decode(&mut rlp_buf.as_slice()).unwrap());
243 }
244}