1use alloy_primitives::{Address, B256};
15use bal_codec::BlockAccessIndex;
16
17pub const SCHEMA_VERSION: u32 = 3;
20
21pub const OLDEST_UPGRADABLE: u32 = 1;
28
29pub const SLOT_KEY_LEN: usize = 20 + 32 + 8 + 4;
30pub const SLOT_PREFIX_LEN: usize = 20 + 32;
31pub const BLOCKIDX_KEY_LEN: usize = 20 + 8;
32pub const LEGACY_BLOCKIDX_KEY_LEN: usize = 20 + 8 + 32;
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[repr(u8)]
39pub enum Provenance {
40 Bal = 0,
42 Proof = 1,
44 Imported = 2,
46 Unverified = 3,
49}
50
51impl Provenance {
52 pub fn from_byte(b: u8) -> Option<Self> {
54 match b {
55 0 => Some(Self::Bal),
56 1 => Some(Self::Proof),
57 2 => Some(Self::Imported),
58 3 => Some(Self::Unverified),
59 _ => None,
60 }
61 }
62
63 pub fn is_verified(self) -> bool {
65 matches!(self, Self::Bal | Self::Proof)
66 }
67}
68
69pub(crate) fn slot_key(
70 addr: Address,
71 slot: B256,
72 block: u64,
73 index: BlockAccessIndex,
74) -> [u8; SLOT_KEY_LEN] {
75 let mut k = [0u8; SLOT_KEY_LEN];
76 k[..20].copy_from_slice(addr.as_slice());
77 k[20..52].copy_from_slice(slot.as_slice());
78 k[52..60].copy_from_slice(&block.to_be_bytes());
79 k[60..64].copy_from_slice(&index.to_be_bytes());
80 k
81}
82
83pub fn slot_prefix(addr: Address, slot: B256) -> [u8; SLOT_PREFIX_LEN] {
84 let mut k = [0u8; SLOT_PREFIX_LEN];
85 k[..20].copy_from_slice(addr.as_slice());
86 k[20..].copy_from_slice(slot.as_slice());
87 k
88}
89
90pub fn parse_slot_key(k: &[u8]) -> Option<(Address, B256, u64, BlockAccessIndex)> {
91 if k.len() != SLOT_KEY_LEN {
92 return None;
93 }
94 Some((
95 Address::from_slice(&k[..20]),
96 B256::from_slice(&k[20..52]),
97 u64::from_be_bytes(k[52..60].try_into().ok()?),
98 u32::from_be_bytes(k[60..64].try_into().ok()?),
99 ))
100}
101
102pub fn blockidx_key(addr: Address, block: u64) -> [u8; BLOCKIDX_KEY_LEN] {
103 let mut k = [0u8; BLOCKIDX_KEY_LEN];
104 k[..20].copy_from_slice(addr.as_slice());
105 k[20..].copy_from_slice(&block.to_be_bytes());
106 k
107}
108
109pub fn parse_blockidx_key(k: &[u8]) -> Option<(Address, u64)> {
110 if k.len() != BLOCKIDX_KEY_LEN {
111 return None;
112 }
113 Some((
114 Address::from_slice(&k[..20]),
115 u64::from_be_bytes(k[20..28].try_into().ok()?),
116 ))
117}
118
119pub fn parse_legacy_blockidx_key(k: &[u8]) -> Option<(Address, u64, B256)> {
121 if k.len() != LEGACY_BLOCKIDX_KEY_LEN {
122 return None;
123 }
124 Some((
125 Address::from_slice(&k[..20]),
126 u64::from_be_bytes(k[20..28].try_into().ok()?),
127 B256::from_slice(&k[28..]),
128 ))
129}
130
131pub fn encode_slots(slots: &[B256]) -> Vec<u8> {
133 let mut out = Vec::with_capacity(slots.len() * 32);
134 for s in slots {
135 out.extend_from_slice(s.as_slice());
136 }
137 out
138}
139
140pub fn decode_slots(v: &[u8]) -> Option<Vec<B256>> {
141 if !v.len().is_multiple_of(32) {
142 return None;
143 }
144 Some(v.as_chunks::<32>().0.iter().map(B256::from).collect())
145}
146
147pub fn encode_value(p: Provenance, v: B256) -> Vec<u8> {
150 let first = v.iter().position(|b| *b != 0).unwrap_or(32);
151 let mut out = Vec::with_capacity(1 + 32 - first);
152 out.push(p as u8);
153 out.extend_from_slice(&v[first..]);
154 out
155}
156
157pub fn decode_value(v: &[u8]) -> Option<(Provenance, B256)> {
160 if v.is_empty() || v.len() > 33 {
161 return None;
162 }
163 let mut word = [0u8; 32];
164 let body = &v[1..];
165 word[32 - body.len()..].copy_from_slice(body);
166 Some((Provenance::from_byte(v[0])?, B256::from(word)))
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub enum BootState {
172 Done,
174 Pending {
176 first_seen: u64,
178 },
179 Lost {
182 first_seen: u64,
184 },
185}
186
187pub fn encode_boot(s: BootState) -> [u8; 9] {
188 let mut out = [0u8; 9];
189 let (tag, n) = match s {
190 BootState::Done => (0u8, 0u64),
191 BootState::Pending { first_seen } => (1, first_seen),
192 BootState::Lost { first_seen } => (2, first_seen),
193 };
194 out[0] = tag;
195 out[1..].copy_from_slice(&n.to_be_bytes());
196 out
197}
198
199pub fn decode_boot(v: &[u8]) -> Option<BootState> {
200 if v.len() != 9 {
201 return None;
202 }
203 let n = u64::from_be_bytes(v[1..].try_into().ok()?);
204 match v[0] {
205 0 => Some(BootState::Done),
206 1 => Some(BootState::Pending { first_seen: n }),
207 2 => Some(BootState::Lost { first_seen: n }),
208 _ => None,
209 }
210}
211
212pub fn prefix_end(prefix: &[u8]) -> Option<Vec<u8>> {
215 let mut end = prefix.to_vec();
216 for i in (0..end.len()).rev() {
217 if end[i] != 0xFF {
218 end[i] += 1;
219 end.truncate(i + 1);
220 return Some(end);
221 }
222 }
223 None
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn keys_order_numerically() {
232 let a = Address::repeat_byte(1);
233 let s = B256::repeat_byte(2);
234 assert!(slot_key(a, s, 255, 0) < slot_key(a, s, 256, 0));
235 assert!(slot_key(a, s, 256, 1) < slot_key(a, s, 256, 2));
236 assert!(slot_key(a, s, 256, u32::MAX) < slot_key(a, s, 257, 0));
237 assert_eq!(parse_slot_key(&slot_key(a, s, 7, 9)), Some((a, s, 7, 9)));
238 }
239
240 #[test]
241 fn prefix_end_increments() {
242 assert_eq!(prefix_end(&[1, 2, 3]), Some(vec![1, 2, 4]));
243 assert_eq!(prefix_end(&[1, 0xFF]), Some(vec![2]));
244 assert_eq!(prefix_end(&[0xFF, 0xFF]), None);
245 }
246
247 #[test]
248 fn value_roundtrip() {
249 let v = B256::repeat_byte(9);
250 assert_eq!(
251 decode_value(&encode_value(Provenance::Proof, v)),
252 Some((Provenance::Proof, v))
253 );
254 let one = B256::from(alloy_primitives::U256::from(1u8).to_be_bytes::<32>());
257 assert_eq!(encode_value(Provenance::Bal, one), vec![0, 1]);
258 assert_eq!(encode_value(Provenance::Bal, B256::ZERO), vec![0]);
259 assert_eq!(decode_value(&[0, 1]), Some((Provenance::Bal, one)));
260 let mut fixed = vec![1u8];
261 fixed.extend_from_slice(v.as_slice());
262 assert_eq!(decode_value(&fixed), Some((Provenance::Proof, v)));
263 assert_eq!(decode_slots(&encode_slots(&[v, one])), Some(vec![v, one]));
264 assert_eq!(decode_slots(&[1, 2, 3]), None);
265 assert_eq!(
266 decode_boot(&encode_boot(BootState::Pending { first_seen: 5 })),
267 Some(BootState::Pending { first_seen: 5 })
268 );
269 }
270}