r402_evm/batch_settlement/
store.rs1use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9use alloy_primitives::B256;
10
11use super::types::ChannelState;
12use crate::chain::TokenAmount;
13
14pub trait ChannelStore: Send + Sync {
16 fn get(&self, channel_id: &B256) -> ChannelState;
18
19 fn put(&self, channel_id: B256, state: ChannelState);
21
22 fn try_charge(
30 &self,
31 channel_id: B256,
32 charge: TokenAmount,
33 max_claimable: TokenAmount,
34 ) -> bool {
35 let mut state = self.get(&channel_id);
36 let next = state.charged_cumulative.0.saturating_add(charge.0);
37 if next > max_claimable.0 {
38 return false;
39 }
40 state.charged_cumulative = TokenAmount::from(next);
41 self.put(channel_id, state);
42 true
43 }
44}
45
46#[derive(Debug, Default, Clone)]
48pub struct MemoryChannelStore {
49 inner: Arc<Mutex<HashMap<B256, ChannelState>>>,
50}
51
52impl MemoryChannelStore {
53 #[must_use]
55 pub fn new() -> Self {
56 Self::default()
57 }
58}
59
60impl ChannelStore for MemoryChannelStore {
61 fn get(&self, channel_id: &B256) -> ChannelState {
62 self.inner
63 .lock()
64 .unwrap_or_else(std::sync::PoisonError::into_inner)
65 .get(channel_id)
66 .copied()
67 .unwrap_or_default()
68 }
69
70 fn put(&self, channel_id: B256, state: ChannelState) {
71 let _ = self
72 .inner
73 .lock()
74 .unwrap_or_else(std::sync::PoisonError::into_inner)
75 .insert(channel_id, state);
76 }
77
78 fn try_charge(
79 &self,
80 channel_id: B256,
81 charge: TokenAmount,
82 max_claimable: TokenAmount,
83 ) -> bool {
84 let mut guard = self
85 .inner
86 .lock()
87 .unwrap_or_else(std::sync::PoisonError::into_inner);
88 let entry = guard.entry(channel_id).or_default();
89 let next = entry.charged_cumulative.0.saturating_add(charge.0);
90 if next > max_claimable.0 {
91 return false;
92 }
93 entry.charged_cumulative = TokenAmount::from(next);
94 drop(guard);
95 true
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use alloy_primitives::U256;
102
103 use super::*;
104
105 #[test]
106 fn charge_monotonic() {
107 let store = MemoryChannelStore::new();
108 let id = B256::repeat_byte(0xab);
109 assert!(store.try_charge(
110 id,
111 TokenAmount::from(U256::from(10_u64)),
112 TokenAmount::from(U256::from(100_u64))
113 ));
114 assert_eq!(store.get(&id).charged_cumulative.0, U256::from(10_u64));
115 assert!(!store.try_charge(
116 id,
117 TokenAmount::from(U256::from(100_u64)),
118 TokenAmount::from(U256::from(100_u64))
119 ));
120 assert!(store.try_charge(
121 id,
122 TokenAmount::from(U256::from(90_u64)),
123 TokenAmount::from(U256::from(100_u64))
124 ));
125 }
126}