use agglayer_primitives::{keccak::keccak256, Digest, U256};
use serde::{Deserialize, Serialize};
use crate::{InvalidRollupIndexError, NetworkId, RollupIndex};
#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, PartialOrd, Ord, Eq)]
pub struct GlobalIndex {
mainnet_flag: bool,
rollup_index: RollupIndex,
leaf_index: u32,
}
#[cfg(feature = "testutils")]
impl arbitrary::Arbitrary<'_> for GlobalIndex {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let mainnet_flag = u.arbitrary()?;
let rollup_index = if mainnet_flag {
RollupIndex::new(0).unwrap()
} else {
RollupIndex::arbitrary(u)?
};
let leaf_index = u.arbitrary()?;
Ok(Self {
mainnet_flag,
rollup_index,
leaf_index,
})
}
}
impl GlobalIndex {
const MAINNET_FLAG_LSB_OFFSET: usize = 2 * 32;
#[inline]
pub fn new(network: NetworkId, leaf_index: u32) -> Self {
let mainnet_flag = network == NetworkId::ETH_L1;
let rollup_index = RollupIndex::new(network.to_u32()).unwrap();
Self {
mainnet_flag,
rollup_index,
leaf_index,
}
}
#[inline]
pub fn is_mainnet(&self) -> bool {
self.mainnet_flag
}
#[inline]
pub fn network_id(&self) -> NetworkId {
if self.mainnet_flag {
NetworkId::new(0)
} else {
self.rollup_index.into()
}
}
#[inline]
pub fn rollup_index(&self) -> Option<RollupIndex> {
if self.mainnet_flag {
None
} else {
Some(self.rollup_index)
}
}
#[inline]
pub fn leaf_index(&self) -> u32 {
self.leaf_index
}
#[inline]
pub fn hash(&self) -> Digest {
let global_index: U256 = (*self).into();
keccak256(global_index.as_le_slice())
}
pub fn into_u256(self) -> U256 {
Into::<U256>::into(self)
}
pub fn from_u256(value: U256) -> Result<Self, InvalidGlobalIndexError> {
TryFrom::<U256>::try_from(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum InvalidGlobalIndexError {
#[error(transparent)]
InvalidRollupIndex(InvalidRollupIndexError),
}
impl TryFrom<U256> for GlobalIndex {
type Error = InvalidGlobalIndexError;
#[inline]
fn try_from(value: U256) -> Result<Self, Self::Error> {
let bytes = value.as_le_slice();
let mainnet_flag = value.bit(Self::MAINNET_FLAG_LSB_OFFSET);
let rollup_index = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
let leaf_index = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
Ok(Self {
mainnet_flag,
rollup_index: rollup_index
.try_into()
.map_err(InvalidGlobalIndexError::InvalidRollupIndex)?,
leaf_index,
})
}
}
impl From<GlobalIndex> for U256 {
#[inline]
fn from(value: GlobalIndex) -> Self {
let mut bytes = [0u8; 32];
let leaf_bytes = value.leaf_index.to_le_bytes();
bytes[0..4].copy_from_slice(&leaf_bytes);
let rollup_bytes = value.rollup_index.to_le_bytes();
bytes[4..8].copy_from_slice(&rollup_bytes);
if value.mainnet_flag {
bytes[8] |= 0x01;
}
U256::from_le_bytes(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check(raw: &str, expected: GlobalIndex) {
let global_index_u256 = U256::from_str_radix(raw, 10).unwrap();
assert_eq!(
global_index_u256,
GlobalIndex::try_from(global_index_u256)
.unwrap()
.into_u256()
);
assert_eq!(expected, GlobalIndex::try_from(global_index_u256).unwrap());
}
#[test]
fn test_global_index() {
check(
"18446744073709748107",
GlobalIndex {
mainnet_flag: true,
rollup_index: RollupIndex::new(0).unwrap(),
leaf_index: 196491,
},
);
check(
"4294968029",
GlobalIndex {
mainnet_flag: false,
rollup_index: RollupIndex::new(1).unwrap(),
leaf_index: 733,
},
);
}
#[test]
fn test_invalid_global_index() {
assert!(
GlobalIndex::try_from(U256::from_str_radix("FFFFFFFF12345678", 16).unwrap()).is_err(),
"Invalid rollup index should fail to parse",
);
}
}