kona_protocol/
output_root.rs

1//! The [`OutputRoot`] type.
2
3use alloy_primitives::{B256, keccak256};
4use derive_more::Display;
5
6/// The [`OutputRoot`] is a high-level commitment to an L2 block. It lifts the state root from the
7/// block header as well as the storage root of the [Predeploys::L2_TO_L1_MESSAGE_PASSER] account
8/// into the top-level commitment construction.
9///
10/// <https://specs.optimism.io/protocol/proposals.html#l2-output-commitment-construction>
11///
12/// [Predeploys::L2_TO_L1_MESSAGE_PASSER]: crate::Predeploys::L2_TO_L1_MESSAGE_PASSER
13#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Hash)]
14#[display("OutputRootV0({}, {}, {})", state_root, bridge_storage_root, block_hash)]
15pub struct OutputRoot {
16    /// The state root of the block corresponding to the output root.
17    pub state_root: B256,
18    /// The storage root of the `L2ToL1MessagePasser` predeploy at the block corresponding to the
19    /// output root.
20    pub bridge_storage_root: B256,
21    /// The block hash that the output root represents.
22    pub block_hash: B256,
23}
24
25impl OutputRoot {
26    /// The encoded length of a V0 output root.
27    pub const ENCODED_LENGTH: usize = 128;
28
29    /// The version of the [`OutputRoot`]. Currently, the protocol only supports one version of this
30    /// commitment.
31    pub const VERSION: u8 = 0;
32
33    /// Returns the version of the [`OutputRoot`]. Currently, the protocol only supports the version
34    /// number 0.
35    pub const fn version(&self) -> B256 {
36        B256::ZERO
37    }
38
39    /// Constructs a V0 [`OutputRoot`] from its parts.
40    pub const fn from_parts(state_root: B256, bridge_storage_root: B256, block_hash: B256) -> Self {
41        Self { state_root, bridge_storage_root, block_hash }
42    }
43
44    /// Encodes the [`OutputRoot`].
45    pub fn encode(&self) -> [u8; Self::ENCODED_LENGTH] {
46        let mut encoded = [0u8; Self::ENCODED_LENGTH];
47        encoded[31] = Self::VERSION;
48        encoded[32..64].copy_from_slice(self.state_root.as_slice());
49        encoded[64..96].copy_from_slice(self.bridge_storage_root.as_slice());
50        encoded[96..128].copy_from_slice(self.block_hash.as_slice());
51        encoded
52    }
53
54    /// Encodes and hashes the [`OutputRoot`].
55    pub fn hash(&self) -> B256 {
56        keccak256(self.encode())
57    }
58}
59
60#[cfg(test)]
61mod test {
62    use super::OutputRoot;
63    use alloy_primitives::{B256, Bytes, b256, bytes};
64
65    fn test_or() -> OutputRoot {
66        OutputRoot::from_parts(
67            B256::left_padding_from(&[0xbe, 0xef]),
68            B256::left_padding_from(&[0xba, 0xbe]),
69            B256::left_padding_from(&[0xc0, 0xde]),
70        )
71    }
72
73    #[test]
74    fn test_hash_output_root() {
75        const EXPECTED_HASH: B256 =
76            b256!("0c39fb6b07cf6694b13e63e59f7b15255be1c93a4d6d3e0da6c99729647c0d11");
77
78        let root = test_or();
79        assert_eq!(root.hash(), EXPECTED_HASH);
80    }
81
82    #[test]
83    fn test_encode_output_root() {
84        const EXPECTED_ENCODING: Bytes = bytes!(
85            "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000beef000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000c0de"
86        );
87
88        let root = OutputRoot::from_parts(
89            B256::left_padding_from(&[0xbe, 0xef]),
90            B256::left_padding_from(&[0xba, 0xbe]),
91            B256::left_padding_from(&[0xc0, 0xde]),
92        );
93
94        assert_eq!(root.encode().as_ref(), EXPECTED_ENCODING.as_ref());
95    }
96}