tempo_transaction_pool/
amm.rs1use std::{
2 collections::{HashMap, VecDeque},
3 sync::Arc,
4};
5
6use alloy_primitives::{Address, U256};
7use parking_lot::RwLock;
8use reth_primitives_traits::{BlockHeader, SealedHeader};
9use reth_provider::{
10 BlockReader, ChainSpecProvider, ExecutionOutcome, ProviderError, ProviderResult, StateProvider,
11 StateProviderFactory,
12};
13use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
14use tempo_precompiles::{
15 DEFAULT_FEE_TOKEN_POST_ALLEGRETTO, DEFAULT_FEE_TOKEN_PRE_ALLEGRETTO, TIP_FEE_MANAGER_ADDRESS,
16 tip_fee_manager::{
17 TipFeeManager,
18 amm::{Pool, PoolKey, compute_amount_out},
19 },
20 tip20::{address_to_token_id_unchecked, token_id_to_address},
21};
22use tempo_primitives::TempoReceipt;
23use tempo_revm::IntoAddress;
24
25const LAST_SEEN_TOKENS_WINDOW: usize = 100;
27
28#[derive(Debug, Clone)]
29pub struct AmmLiquidityCache {
30 inner: Arc<RwLock<AmmLiquidityCacheInner>>,
31}
32
33impl AmmLiquidityCache {
34 pub fn new<Client>(client: Client) -> ProviderResult<Self>
37 where
38 Client: StateProviderFactory + BlockReader + ChainSpecProvider<ChainSpec = TempoChainSpec>,
39 {
40 let this = Self {
41 inner: Default::default(),
42 };
43 let tip = client.best_block_number()?;
44
45 for header in client
46 .sealed_headers_range(tip.saturating_sub(LAST_SEEN_TOKENS_WINDOW as u64 + 1)..=tip)?
47 {
48 this.on_new_block(&header, &client)?;
49 }
50
51 Ok(this)
52 }
53
54 pub fn has_enough_liquidity(
57 &self,
58 user_token: Address,
59 fee: U256,
60 state_provider: &impl StateProvider,
61 ) -> Result<bool, ProviderError> {
62 let user_id = address_to_token_id_unchecked(user_token);
63 let amount_out = compute_amount_out(fee).map_err(ProviderError::other)?;
64
65 let mut missing_in_cache = Vec::new();
66
67 {
69 let inner = self.inner.read();
70 for token in &inner.unique_tokens {
71 if token == &user_token {
75 return Ok(true);
76 }
77
78 let validator_id = address_to_token_id_unchecked(*token);
79
80 if let Some(validator_reserve) = inner.cache.get(&(user_id, validator_id)) {
81 if *validator_reserve >= amount_out {
82 return Ok(true);
83 }
84 } else {
85 missing_in_cache.push(validator_id);
86 }
87 }
88 }
89
90 if missing_in_cache.is_empty() {
92 return Ok(false);
93 }
94
95 for token in missing_in_cache {
97 let pool_key =
99 PoolKey::new(token_id_to_address(user_id), token_id_to_address(token)).get_id();
100 let slot = TipFeeManager::new().pools.at(pool_key).base_slot();
101 let pool = state_provider
102 .storage(TIP_FEE_MANAGER_ADDRESS, slot.into())?
103 .unwrap_or_default();
104 let reserve = U256::from(Pool::decode_from_slot(pool).reserve_validator_token);
105
106 let mut inner = self.inner.write();
107 inner.cache.insert((user_id, token), reserve);
108 inner.slot_to_pool.insert(slot, (user_id, token));
109
110 if reserve >= amount_out {
112 return Ok(true);
113 }
114 }
115
116 Ok(false)
117 }
118
119 pub fn on_new_state(&self, execution_outcome: &ExecutionOutcome<TempoReceipt>) {
122 let Some(storage) = execution_outcome
123 .account_state(&TIP_FEE_MANAGER_ADDRESS)
124 .map(|acc| &acc.storage)
125 else {
126 return;
127 };
128
129 let mut inner = self.inner.write();
130
131 for (slot, value) in storage.iter() {
133 if let Some(pool) = inner.slot_to_pool.get(slot).copied() {
134 let validator_reserve =
136 U256::from(Pool::decode_from_slot(value.present_value).reserve_validator_token);
137 inner.cache.insert(pool, validator_reserve);
138 } else if let Some(validator) = inner.slot_to_validator.get(slot).copied() {
139 inner
141 .validator_preferences
142 .insert(validator, value.present_value().into_address());
143 }
144 }
145 }
146
147 pub fn on_new_block<P>(
149 &self,
150 header: &SealedHeader<impl BlockHeader>,
151 state: P,
152 ) -> ProviderResult<()>
153 where
154 P: StateProviderFactory + ChainSpecProvider<ChainSpec: TempoHardforks>,
155 {
156 let beneficiary = header.beneficiary();
157 let validator_token_slot = TipFeeManager::new().validator_tokens.at(beneficiary).slot();
158
159 let cached_preference = self
160 .inner
161 .read()
162 .validator_preferences
163 .get(&beneficiary)
164 .copied();
165
166 let preference = if let Some(cached) = cached_preference {
167 cached
168 } else {
169 state
171 .state_by_block_hash(header.hash())?
172 .storage(TIP_FEE_MANAGER_ADDRESS, validator_token_slot.into())?
173 .unwrap_or_default()
174 .into_address()
175 };
176
177 let fee_token = if preference.is_zero() {
179 let chain_spec = state.chain_spec();
180 if chain_spec.is_allegretto_active_at_timestamp(header.timestamp()) {
181 DEFAULT_FEE_TOKEN_POST_ALLEGRETTO
182 } else {
183 DEFAULT_FEE_TOKEN_PRE_ALLEGRETTO
184 }
185 } else {
186 preference
187 };
188
189 let mut inner = self.inner.write();
190
191 if cached_preference.is_none() {
193 inner.validator_preferences.insert(beneficiary, preference);
194 inner
195 .slot_to_validator
196 .insert(validator_token_slot, beneficiary);
197 }
198
199 inner.last_seen_tokens.push_back(fee_token);
201 if inner.last_seen_tokens.len() > LAST_SEEN_TOKENS_WINDOW {
202 inner.last_seen_tokens.pop_front();
203 }
204
205 inner.unique_tokens = inner.last_seen_tokens.iter().copied().collect();
207
208 Ok(())
209 }
210}
211
212#[derive(Debug, Default)]
213struct AmmLiquidityCacheInner {
214 cache: HashMap<(u64, u64), U256>,
216
217 slot_to_pool: HashMap<U256, (u64, u64)>,
219
220 last_seen_tokens: VecDeque<Address>,
222
223 unique_tokens: Vec<Address>,
227
228 validator_preferences: HashMap<Address, Address>,
230
231 slot_to_validator: HashMap<U256, Address>,
233}