use agglayer_primitives::{keccak::keccak256_combine, Digest};
use agglayer_tries::utils::empty_hash_array_at_height;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use thiserror::Error;
pub mod proof;
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalExitTree<const TREE_DEPTH: usize = 32> {
pub leaf_count: u32,
#[serde_as(as = "[_; TREE_DEPTH]")]
pub frontier: [Digest; TREE_DEPTH],
}
#[derive(Clone, Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
pub enum LocalExitTreeError {
#[error("Leaf index overflow")]
LeafIndexOverflow,
#[error("Index out of bounds")]
IndexOutOfBounds,
#[error("Frontier index out of bounds")]
FrontierIndexOutOfBounds,
}
impl<const TREE_DEPTH: usize> Default for LocalExitTree<TREE_DEPTH> {
#[inline]
fn default() -> Self {
Self {
leaf_count: 0,
frontier: [Digest::default(); TREE_DEPTH],
}
}
}
impl<const TREE_DEPTH: usize> LocalExitTree<TREE_DEPTH> {
const MAX_NUM_LEAVES: u32 = ((1u64 << TREE_DEPTH) - 1) as u32;
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn leaf_count(&self) -> u32 {
self.leaf_count
}
#[inline]
pub fn frontier(&self) -> [Digest; TREE_DEPTH] {
self.frontier
}
#[inline]
pub fn from_leaves(leaves: impl Iterator<Item = Digest>) -> Result<Self, LocalExitTreeError> {
let mut tree = Self::new();
for leaf in leaves {
tree.add_leaf(leaf)?;
}
Ok(tree)
}
#[inline]
pub fn from_parts(leaf_count: u32, frontier: [Digest; TREE_DEPTH]) -> Self {
Self {
leaf_count,
frontier,
}
}
#[inline]
pub fn add_leaf(&mut self, leaf: Digest) -> Result<u32, LocalExitTreeError> {
if self.leaf_count >= Self::MAX_NUM_LEAVES {
return Err(LocalExitTreeError::LeafIndexOverflow);
}
let frontier_insertion_index: usize = {
let leaf_count_after_insertion = self.leaf_count + 1;
leaf_count_after_insertion.trailing_zeros() as usize
};
let new_frontier_entry = {
let mut entry = leaf;
for frontier_ele in &self.frontier[0..frontier_insertion_index] {
entry = keccak256_combine([frontier_ele, &entry]);
}
entry
};
self.frontier[frontier_insertion_index] = new_frontier_entry;
self.leaf_count = self
.leaf_count
.checked_add(1)
.ok_or(LocalExitTreeError::LeafIndexOverflow)?;
Ok(self.leaf_count)
}
#[inline]
pub fn get_root(&self) -> Digest {
let mut root = Digest::default();
for (height, empty_hash_at_height) in empty_hash_array_at_height::<TREE_DEPTH>()
.iter()
.enumerate()
{
if get_bit_at(self.leaf_count, height) == 1 {
root = keccak256_combine([&self.frontier[height], &root]);
} else {
root = keccak256_combine([&root, empty_hash_at_height]);
}
}
root
}
}
#[inline]
fn get_bit_at(target: u32, bit_idx: usize) -> u32 {
(target >> bit_idx) & 1
}
#[cfg(test)]
mod tests {
use agglayer_primitives::{Address, Hashable, U256};
use crate::{bridge_exit::BridgeExit, local_exit_tree::LocalExitTree, token_info::LeafType};
#[test]
fn test_deposit_hash() {
let mut deposit = BridgeExit::new(
LeafType::Transfer,
0.into(),
Address::ZERO,
1.into(),
Address::ZERO,
U256::default(),
vec![],
);
let amount_bytes = hex::decode("8ac7230489e80000").unwrap_or_default();
deposit.amount = U256::try_from_be_slice(amount_bytes.as_slice()).unwrap();
let dest_addr = hex::decode("c949254d682d8c9ad5682521675b8f43b102aec4").unwrap_or_default();
deposit.dest_address.as_mut().copy_from_slice(&dest_addr);
let leaf_hash = deposit.hash();
assert_eq!(
"22ed288677b4c2afd83a6d7d55f7df7f4eaaf60f7310210c030fd27adacbc5e0",
hex::encode(leaf_hash)
);
let mut dm = LocalExitTree::<32>::new();
dm.add_leaf(leaf_hash).unwrap();
let dm_root = dm.get_root();
assert_eq!(
"5ba002329b53c11a2f1dfe90b11e031771842056cf2125b43da8103c199dcd7f",
hex::encode(dm_root.as_slice())
);
}
}