unified_bridge/local_exit_tree/
mod.rs1use agglayer_primitives::{keccak::keccak256_combine, Digest};
2use agglayer_tries::utils::empty_hash_array_at_height;
3use serde::{Deserialize, Serialize};
4use serde_with::serde_as;
5use thiserror::Error;
6
7pub mod proof;
8
9#[serde_as]
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct LocalExitTree<const TREE_DEPTH: usize = 32> {
13 pub leaf_count: u32,
15
16 #[serde_as(as = "[_; TREE_DEPTH]")]
23 pub frontier: [Digest; TREE_DEPTH],
24}
25
26#[derive(Clone, Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
27pub enum LocalExitTreeError {
28 #[error("Leaf index overflow")]
29 LeafIndexOverflow,
30
31 #[error("Index out of bounds")]
32 IndexOutOfBounds,
33
34 #[error("Frontier index out of bounds")]
35 FrontierIndexOutOfBounds,
36}
37
38impl<const TREE_DEPTH: usize> Default for LocalExitTree<TREE_DEPTH> {
39 #[inline]
40 fn default() -> Self {
41 Self {
42 leaf_count: 0,
43 frontier: [Digest::default(); TREE_DEPTH],
44 }
45 }
46}
47
48impl<const TREE_DEPTH: usize> LocalExitTree<TREE_DEPTH> {
49 const MAX_NUM_LEAVES: u32 = ((1u64 << TREE_DEPTH) - 1) as u32;
50
51 #[inline]
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 #[inline]
58 pub fn leaf_count(&self) -> u32 {
59 self.leaf_count
60 }
61
62 #[inline]
63 pub fn frontier(&self) -> [Digest; TREE_DEPTH] {
64 self.frontier
65 }
66
67 #[inline]
69 pub fn from_leaves(leaves: impl Iterator<Item = Digest>) -> Result<Self, LocalExitTreeError> {
70 let mut tree = Self::new();
71
72 for leaf in leaves {
73 tree.add_leaf(leaf)?;
74 }
75
76 Ok(tree)
77 }
78
79 #[inline]
82 pub fn from_parts(leaf_count: u32, frontier: [Digest; TREE_DEPTH]) -> Self {
83 Self {
84 leaf_count,
85 frontier,
86 }
87 }
88 #[inline]
90 pub fn add_leaf(&mut self, leaf: Digest) -> Result<u32, LocalExitTreeError> {
91 if self.leaf_count >= Self::MAX_NUM_LEAVES {
92 return Err(LocalExitTreeError::LeafIndexOverflow);
93 }
94 let frontier_insertion_index: usize = {
96 let leaf_count_after_insertion = self.leaf_count + 1;
97
98 leaf_count_after_insertion.trailing_zeros() as usize
99 };
100
101 let new_frontier_entry = {
103 let mut entry = leaf;
104 for frontier_ele in &self.frontier[0..frontier_insertion_index] {
105 entry = keccak256_combine([frontier_ele, &entry]);
106 }
107
108 entry
109 };
110
111 self.frontier[frontier_insertion_index] = new_frontier_entry;
113 self.leaf_count = self
114 .leaf_count
115 .checked_add(1)
116 .ok_or(LocalExitTreeError::LeafIndexOverflow)?;
117
118 Ok(self.leaf_count)
119 }
120
121 #[inline]
123 pub fn get_root(&self) -> Digest {
124 let mut root = Digest::default();
127
128 for (height, empty_hash_at_height) in empty_hash_array_at_height::<TREE_DEPTH>()
129 .iter()
130 .enumerate()
131 {
132 if get_bit_at(self.leaf_count, height) == 1 {
133 root = keccak256_combine([&self.frontier[height], &root]);
134 } else {
135 root = keccak256_combine([&root, empty_hash_at_height]);
136 }
137 }
138
139 root
140 }
141}
142
143#[inline]
145fn get_bit_at(target: u32, bit_idx: usize) -> u32 {
146 (target >> bit_idx) & 1
147}
148
149#[cfg(test)]
150mod tests {
151 use agglayer_primitives::{Address, Hashable, U256};
152
153 use crate::{bridge_exit::BridgeExit, local_exit_tree::LocalExitTree, token_info::LeafType};
154
155 #[test]
156 fn test_deposit_hash() {
157 let mut deposit = BridgeExit::new(
158 LeafType::Transfer,
159 0.into(),
160 Address::ZERO,
161 1.into(),
162 Address::ZERO,
163 U256::default(),
164 vec![],
165 );
166
167 let amount_bytes = hex::decode("8ac7230489e80000").unwrap_or_default();
168 deposit.amount = U256::try_from_be_slice(amount_bytes.as_slice()).unwrap();
169
170 let dest_addr = hex::decode("c949254d682d8c9ad5682521675b8f43b102aec4").unwrap_or_default();
171 deposit.dest_address.as_mut().copy_from_slice(&dest_addr);
172
173 let leaf_hash = deposit.hash();
174 assert_eq!(
175 "22ed288677b4c2afd83a6d7d55f7df7f4eaaf60f7310210c030fd27adacbc5e0",
176 hex::encode(leaf_hash)
177 );
178
179 let mut dm = LocalExitTree::<32>::new();
180 dm.add_leaf(leaf_hash).unwrap();
181 let dm_root = dm.get_root();
182 assert_eq!(
183 "5ba002329b53c11a2f1dfe90b11e031771842056cf2125b43da8103c199dcd7f",
184 hex::encode(dm_root.as_slice())
185 );
186 }
187}